1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "dalvik_system_VMDebug.h"
18
19#include <string.h>
20#include <unistd.h>
21
22#include <sstream>
23
24#include "base/histogram-inl.h"
25#include "base/time_utils.h"
26#include "class_linker.h"
27#include "common_throws.h"
28#include "debugger.h"
29#include "gc/space/bump_pointer_space.h"
30#include "gc/space/dlmalloc_space.h"
31#include "gc/space/large_object_space.h"
32#include "gc/space/space-inl.h"
33#include "gc/space/zygote_space.h"
34#include "hprof/hprof.h"
35#include "jni_internal.h"
36#include "mirror/class.h"
37#include "ScopedLocalRef.h"
38#include "ScopedUtfChars.h"
39#include "scoped_fast_native_object_access.h"
40#include "trace.h"
41#include "well_known_classes.h"
42
43namespace art {
44
45static jobjectArray VMDebug_getVmFeatureList(JNIEnv* env, jclass) {
46  static const char* features[] = {
47    "method-trace-profiling",
48    "method-trace-profiling-streaming",
49    "method-sample-profiling",
50    "hprof-heap-dump",
51    "hprof-heap-dump-streaming",
52  };
53  jobjectArray result = env->NewObjectArray(arraysize(features),
54                                            WellKnownClasses::java_lang_String,
55                                            nullptr);
56  if (result != nullptr) {
57    for (size_t i = 0; i < arraysize(features); ++i) {
58      ScopedLocalRef<jstring> jfeature(env, env->NewStringUTF(features[i]));
59      if (jfeature.get() == nullptr) {
60        return nullptr;
61      }
62      env->SetObjectArrayElement(result, i, jfeature.get());
63    }
64  }
65  return result;
66}
67
68static void VMDebug_startAllocCounting(JNIEnv*, jclass) {
69  Runtime::Current()->SetStatsEnabled(true);
70}
71
72static void VMDebug_stopAllocCounting(JNIEnv*, jclass) {
73  Runtime::Current()->SetStatsEnabled(false);
74}
75
76static jint VMDebug_getAllocCount(JNIEnv*, jclass, jint kind) {
77  return Runtime::Current()->GetStat(kind);
78}
79
80static void VMDebug_resetAllocCount(JNIEnv*, jclass, jint kinds) {
81  Runtime::Current()->ResetStats(kinds);
82}
83
84static void VMDebug_startMethodTracingDdmsImpl(JNIEnv*, jclass, jint bufferSize, jint flags,
85                                               jboolean samplingEnabled, jint intervalUs) {
86  Trace::Start("[DDMS]", -1, bufferSize, flags, Trace::TraceOutputMode::kDDMS,
87               samplingEnabled ? Trace::TraceMode::kSampling : Trace::TraceMode::kMethodTracing,
88               intervalUs);
89}
90
91static void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceFilename,
92                                         jobject javaFd, jint bufferSize, jint flags,
93                                         jboolean samplingEnabled, jint intervalUs) {
94  int originalFd = jniGetFDFromFileDescriptor(env, javaFd);
95  if (originalFd < 0) {
96    return;
97  }
98
99  int fd = dup(originalFd);
100  if (fd < 0) {
101    ScopedObjectAccess soa(env);
102    soa.Self()->ThrowNewExceptionF("Ljava/lang/RuntimeException;",
103                                   "dup(%d) failed: %s", originalFd, strerror(errno));
104    return;
105  }
106
107  ScopedUtfChars traceFilename(env, javaTraceFilename);
108  if (traceFilename.c_str() == nullptr) {
109    return;
110  }
111  Trace::Start(traceFilename.c_str(), fd, bufferSize, flags, Trace::TraceOutputMode::kFile,
112               samplingEnabled ? Trace::TraceMode::kSampling : Trace::TraceMode::kMethodTracing,
113               intervalUs);
114}
115
116static void VMDebug_startMethodTracingFilename(JNIEnv* env, jclass, jstring javaTraceFilename,
117                                               jint bufferSize, jint flags,
118                                               jboolean samplingEnabled, jint intervalUs) {
119  ScopedUtfChars traceFilename(env, javaTraceFilename);
120  if (traceFilename.c_str() == nullptr) {
121    return;
122  }
123  Trace::Start(traceFilename.c_str(), -1, bufferSize, flags, Trace::TraceOutputMode::kFile,
124               samplingEnabled ? Trace::TraceMode::kSampling : Trace::TraceMode::kMethodTracing,
125               intervalUs);
126}
127
128static jint VMDebug_getMethodTracingMode(JNIEnv*, jclass) {
129  return Trace::GetMethodTracingMode();
130}
131
132static void VMDebug_stopMethodTracing(JNIEnv*, jclass) {
133  Trace::Stop();
134}
135
136static void VMDebug_startEmulatorTracing(JNIEnv*, jclass) {
137  UNIMPLEMENTED(WARNING);
138  // dvmEmulatorTraceStart();
139}
140
141static void VMDebug_stopEmulatorTracing(JNIEnv*, jclass) {
142  UNIMPLEMENTED(WARNING);
143  // dvmEmulatorTraceStop();
144}
145
146static jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) {
147  return Dbg::IsDebuggerActive();
148}
149
150static jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) {
151  return Dbg::IsJdwpConfigured();
152}
153
154static jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
155  return Dbg::LastDebuggerActivity();
156}
157
158static void ThrowUnsupportedOperationException(JNIEnv* env) {
159  ScopedObjectAccess soa(env);
160  soa.Self()->ThrowNewException("Ljava/lang/UnsupportedOperationException;", nullptr);
161}
162
163static void VMDebug_startInstructionCounting(JNIEnv* env, jclass) {
164  ThrowUnsupportedOperationException(env);
165}
166
167static void VMDebug_stopInstructionCounting(JNIEnv* env, jclass) {
168  ThrowUnsupportedOperationException(env);
169}
170
171static void VMDebug_getInstructionCount(JNIEnv* env, jclass, jintArray /*javaCounts*/) {
172  ThrowUnsupportedOperationException(env);
173}
174
175static void VMDebug_resetInstructionCount(JNIEnv* env, jclass) {
176  ThrowUnsupportedOperationException(env);
177}
178
179static void VMDebug_printLoadedClasses(JNIEnv* env, jclass, jint flags) {
180  ScopedFastNativeObjectAccess soa(env);
181  return Runtime::Current()->GetClassLinker()->DumpAllClasses(flags);
182}
183
184static jint VMDebug_getLoadedClassCount(JNIEnv* env, jclass) {
185  ScopedFastNativeObjectAccess soa(env);
186  return Runtime::Current()->GetClassLinker()->NumLoadedClasses();
187}
188
189/*
190 * Returns the thread-specific CPU-time clock value for the current thread,
191 * or -1 if the feature isn't supported.
192 */
193static jlong VMDebug_threadCpuTimeNanos(JNIEnv*, jclass) {
194  return ThreadCpuNanoTime();
195}
196
197/*
198 * static void dumpHprofData(String fileName, FileDescriptor fd)
199 *
200 * Cause "hprof" data to be dumped.  We can throw an IOException if an
201 * error occurs during file handling.
202 */
203static void VMDebug_dumpHprofData(JNIEnv* env, jclass, jstring javaFilename, jobject javaFd) {
204  // Only one of these may be null.
205  if (javaFilename == nullptr && javaFd == nullptr) {
206    ScopedObjectAccess soa(env);
207    ThrowNullPointerException("fileName == null && fd == null");
208    return;
209  }
210
211  std::string filename;
212  if (javaFilename != nullptr) {
213    ScopedUtfChars chars(env, javaFilename);
214    if (env->ExceptionCheck()) {
215      return;
216    }
217    filename = chars.c_str();
218  } else {
219    filename = "[fd]";
220  }
221
222  int fd = -1;
223  if (javaFd != nullptr) {
224    fd = jniGetFDFromFileDescriptor(env, javaFd);
225    if (fd < 0) {
226      ScopedObjectAccess soa(env);
227      ThrowRuntimeException("Invalid file descriptor");
228      return;
229    }
230  }
231
232  hprof::DumpHeap(filename.c_str(), fd, false);
233}
234
235static void VMDebug_dumpHprofDataDdms(JNIEnv*, jclass) {
236  hprof::DumpHeap("[DDMS]", -1, true);
237}
238
239static void VMDebug_dumpReferenceTables(JNIEnv* env, jclass) {
240  ScopedObjectAccess soa(env);
241  LOG(INFO) << "--- reference table dump ---";
242
243  soa.Env()->DumpReferenceTables(LOG(INFO));
244  soa.Vm()->DumpReferenceTables(LOG(INFO));
245
246  LOG(INFO) << "---";
247}
248
249static void VMDebug_crash(JNIEnv*, jclass) {
250  LOG(FATAL) << "Crashing runtime on request";
251}
252
253static void VMDebug_infopoint(JNIEnv*, jclass, jint id) {
254  LOG(INFO) << "VMDebug infopoint " << id << " hit";
255}
256
257static jlong VMDebug_countInstancesOfClass(JNIEnv* env, jclass, jclass javaClass,
258                                           jboolean countAssignable) {
259  ScopedObjectAccess soa(env);
260  gc::Heap* const heap = Runtime::Current()->GetHeap();
261  // Caller's responsibility to do GC if desired.
262  mirror::Class* c = soa.Decode<mirror::Class*>(javaClass);
263  if (c == nullptr) {
264    return 0;
265  }
266  std::vector<mirror::Class*> classes {c};
267  uint64_t count = 0;
268  heap->CountInstances(classes, countAssignable, &count);
269  return count;
270}
271
272static jlongArray VMDebug_countInstancesOfClasses(JNIEnv* env, jclass, jobjectArray javaClasses,
273                                                  jboolean countAssignable) {
274  ScopedObjectAccess soa(env);
275  gc::Heap* const heap = Runtime::Current()->GetHeap();
276  // Caller's responsibility to do GC if desired.
277  auto* decoded_classes = soa.Decode<mirror::ObjectArray<mirror::Class>*>(javaClasses);
278  if (decoded_classes == nullptr) {
279    return nullptr;
280  }
281  std::vector<mirror::Class*> classes;
282  for (size_t i = 0, count = decoded_classes->GetLength(); i < count; ++i) {
283    classes.push_back(decoded_classes->Get(i));
284  }
285  std::vector<uint64_t> counts(classes.size(), 0u);
286  // Heap::CountInstances can handle null and will put 0 for these classes.
287  heap->CountInstances(classes, countAssignable, &counts[0]);
288  auto* long_counts = mirror::LongArray::Alloc(soa.Self(), counts.size());
289  if (long_counts == nullptr) {
290    soa.Self()->AssertPendingOOMException();
291    return nullptr;
292  }
293  for (size_t i = 0; i < counts.size(); ++i) {
294    long_counts->Set(i, counts[i]);
295  }
296  return soa.AddLocalReference<jlongArray>(long_counts);
297}
298
299// We export the VM internal per-heap-space size/alloc/free metrics
300// for the zygote space, alloc space (application heap), and the large
301// object space for dumpsys meminfo. The other memory region data such
302// as PSS, private/shared dirty/shared data are available via
303// /proc/<pid>/smaps.
304static void VMDebug_getHeapSpaceStats(JNIEnv* env, jclass, jlongArray data) {
305  jlong* arr = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(data, 0));
306  if (arr == nullptr || env->GetArrayLength(data) < 9) {
307    return;
308  }
309
310  size_t allocSize = 0;
311  size_t allocUsed = 0;
312  size_t zygoteSize = 0;
313  size_t zygoteUsed = 0;
314  size_t largeObjectsSize = 0;
315  size_t largeObjectsUsed = 0;
316  gc::Heap* heap = Runtime::Current()->GetHeap();
317  {
318    ScopedObjectAccess soa(env);
319    for (gc::space::ContinuousSpace* space : heap->GetContinuousSpaces()) {
320      if (space->IsImageSpace()) {
321        // Currently don't include the image space.
322      } else if (space->IsZygoteSpace()) {
323        gc::space::ZygoteSpace* zygote_space = space->AsZygoteSpace();
324        zygoteSize += zygote_space->Size();
325        zygoteUsed += zygote_space->GetBytesAllocated();
326      } else if (space->IsMallocSpace()) {
327        // This is a malloc space.
328        gc::space::MallocSpace* malloc_space = space->AsMallocSpace();
329        allocSize += malloc_space->GetFootprint();
330        allocUsed += malloc_space->GetBytesAllocated();
331      } else if (space->IsBumpPointerSpace()) {
332        gc::space::BumpPointerSpace* bump_pointer_space = space->AsBumpPointerSpace();
333        allocSize += bump_pointer_space->Size();
334        allocUsed += bump_pointer_space->GetBytesAllocated();
335      }
336    }
337    for (gc::space::DiscontinuousSpace* space : heap->GetDiscontinuousSpaces()) {
338      if (space->IsLargeObjectSpace()) {
339        largeObjectsSize += space->AsLargeObjectSpace()->GetBytesAllocated();
340        largeObjectsUsed += largeObjectsSize;
341      }
342    }
343  }
344  size_t allocFree = allocSize - allocUsed;
345  size_t zygoteFree = zygoteSize - zygoteUsed;
346  size_t largeObjectsFree = largeObjectsSize - largeObjectsUsed;
347
348  int j = 0;
349  arr[j++] = allocSize;
350  arr[j++] = allocUsed;
351  arr[j++] = allocFree;
352  arr[j++] = zygoteSize;
353  arr[j++] = zygoteUsed;
354  arr[j++] = zygoteFree;
355  arr[j++] = largeObjectsSize;
356  arr[j++] = largeObjectsUsed;
357  arr[j++] = largeObjectsFree;
358  env->ReleasePrimitiveArrayCritical(data, arr, 0);
359}
360
361// The runtime stat names for VMDebug.getRuntimeStat().
362enum class VMDebugRuntimeStatId {
363  kArtGcGcCount = 0,
364  kArtGcGcTime,
365  kArtGcBytesAllocated,
366  kArtGcBytesFreed,
367  kArtGcBlockingGcCount,
368  kArtGcBlockingGcTime,
369  kArtGcGcCountRateHistogram,
370  kArtGcBlockingGcCountRateHistogram,
371  kNumRuntimeStats,
372};
373
374static jobject VMDebug_getRuntimeStatInternal(JNIEnv* env, jclass, jint statId) {
375  gc::Heap* heap = Runtime::Current()->GetHeap();
376  switch (static_cast<VMDebugRuntimeStatId>(statId)) {
377    case VMDebugRuntimeStatId::kArtGcGcCount: {
378      std::string output = std::to_string(heap->GetGcCount());
379      return env->NewStringUTF(output.c_str());
380    }
381    case VMDebugRuntimeStatId::kArtGcGcTime: {
382      std::string output = std::to_string(NsToMs(heap->GetGcTime()));
383      return env->NewStringUTF(output.c_str());
384    }
385    case VMDebugRuntimeStatId::kArtGcBytesAllocated: {
386      std::string output = std::to_string(heap->GetBytesAllocatedEver());
387      return env->NewStringUTF(output.c_str());
388    }
389    case VMDebugRuntimeStatId::kArtGcBytesFreed: {
390      std::string output = std::to_string(heap->GetBytesFreedEver());
391      return env->NewStringUTF(output.c_str());
392    }
393    case VMDebugRuntimeStatId::kArtGcBlockingGcCount: {
394      std::string output = std::to_string(heap->GetBlockingGcCount());
395      return env->NewStringUTF(output.c_str());
396    }
397    case VMDebugRuntimeStatId::kArtGcBlockingGcTime: {
398      std::string output = std::to_string(NsToMs(heap->GetBlockingGcTime()));
399      return env->NewStringUTF(output.c_str());
400    }
401    case VMDebugRuntimeStatId::kArtGcGcCountRateHistogram: {
402      std::ostringstream output;
403      heap->DumpGcCountRateHistogram(output);
404      return env->NewStringUTF(output.str().c_str());
405    }
406    case VMDebugRuntimeStatId::kArtGcBlockingGcCountRateHistogram: {
407      std::ostringstream output;
408      heap->DumpBlockingGcCountRateHistogram(output);
409      return env->NewStringUTF(output.str().c_str());
410    }
411    default:
412      return nullptr;
413  }
414}
415
416static bool SetRuntimeStatValue(JNIEnv* env, jobjectArray result, VMDebugRuntimeStatId id,
417                                std::string value) {
418  ScopedLocalRef<jstring> jvalue(env, env->NewStringUTF(value.c_str()));
419  if (jvalue.get() == nullptr) {
420    return false;
421  }
422  env->SetObjectArrayElement(result, static_cast<jint>(id), jvalue.get());
423  return true;
424}
425
426static jobjectArray VMDebug_getRuntimeStatsInternal(JNIEnv* env, jclass) {
427  jobjectArray result = env->NewObjectArray(
428      static_cast<jint>(VMDebugRuntimeStatId::kNumRuntimeStats),
429      WellKnownClasses::java_lang_String,
430      nullptr);
431  if (result == nullptr) {
432    return nullptr;
433  }
434  gc::Heap* heap = Runtime::Current()->GetHeap();
435  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcGcCount,
436                           std::to_string(heap->GetGcCount()))) {
437    return nullptr;
438  }
439  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcGcTime,
440                           std::to_string(NsToMs(heap->GetGcTime())))) {
441    return nullptr;
442  }
443  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBytesAllocated,
444                           std::to_string(heap->GetBytesAllocatedEver()))) {
445    return nullptr;
446  }
447  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBytesFreed,
448                           std::to_string(heap->GetBytesFreedEver()))) {
449    return nullptr;
450  }
451  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBlockingGcCount,
452                           std::to_string(heap->GetBlockingGcCount()))) {
453    return nullptr;
454  }
455  if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBlockingGcTime,
456                           std::to_string(NsToMs(heap->GetBlockingGcTime())))) {
457    return nullptr;
458  }
459  {
460    std::ostringstream output;
461    heap->DumpGcCountRateHistogram(output);
462    if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcGcCountRateHistogram,
463                             output.str())) {
464      return nullptr;
465    }
466  }
467  {
468    std::ostringstream output;
469    heap->DumpBlockingGcCountRateHistogram(output);
470    if (!SetRuntimeStatValue(env, result, VMDebugRuntimeStatId::kArtGcBlockingGcCountRateHistogram,
471                             output.str())) {
472      return nullptr;
473    }
474  }
475  return result;
476}
477
478static JNINativeMethod gMethods[] = {
479  NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
480  NATIVE_METHOD(VMDebug, countInstancesOfClasses, "([Ljava/lang/Class;Z)[J"),
481  NATIVE_METHOD(VMDebug, crash, "()V"),
482  NATIVE_METHOD(VMDebug, dumpHprofData, "(Ljava/lang/String;Ljava/io/FileDescriptor;)V"),
483  NATIVE_METHOD(VMDebug, dumpHprofDataDdms, "()V"),
484  NATIVE_METHOD(VMDebug, dumpReferenceTables, "()V"),
485  NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
486  NATIVE_METHOD(VMDebug, getHeapSpaceStats, "([J)V"),
487  NATIVE_METHOD(VMDebug, getInstructionCount, "([I)V"),
488  NATIVE_METHOD(VMDebug, getLoadedClassCount, "!()I"),
489  NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
490  NATIVE_METHOD(VMDebug, infopoint, "(I)V"),
491  NATIVE_METHOD(VMDebug, isDebuggerConnected, "!()Z"),
492  NATIVE_METHOD(VMDebug, isDebuggingEnabled, "!()Z"),
493  NATIVE_METHOD(VMDebug, getMethodTracingMode, "()I"),
494  NATIVE_METHOD(VMDebug, lastDebuggerActivity, "!()J"),
495  NATIVE_METHOD(VMDebug, printLoadedClasses, "!(I)V"),
496  NATIVE_METHOD(VMDebug, resetAllocCount, "(I)V"),
497  NATIVE_METHOD(VMDebug, resetInstructionCount, "()V"),
498  NATIVE_METHOD(VMDebug, startAllocCounting, "()V"),
499  NATIVE_METHOD(VMDebug, startEmulatorTracing, "()V"),
500  NATIVE_METHOD(VMDebug, startInstructionCounting, "()V"),
501  NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(IIZI)V"),
502  NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;IIZI)V"),
503  NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;IIZI)V"),
504  NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"),
505  NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"),
506  NATIVE_METHOD(VMDebug, stopInstructionCounting, "()V"),
507  NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
508  NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "!()J"),
509  NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
510  NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;")
511};
512
513void register_dalvik_system_VMDebug(JNIEnv* env) {
514  REGISTER_NATIVE_METHODS("dalvik/system/VMDebug");
515}
516
517}  // namespace art
518