Home | History | Annotate | Download | only in native
      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 
     43 namespace art {
     44 
     45 static 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 
     68 static void VMDebug_startAllocCounting(JNIEnv*, jclass) {
     69   Runtime::Current()->SetStatsEnabled(true);
     70 }
     71 
     72 static void VMDebug_stopAllocCounting(JNIEnv*, jclass) {
     73   Runtime::Current()->SetStatsEnabled(false);
     74 }
     75 
     76 static jint VMDebug_getAllocCount(JNIEnv*, jclass, jint kind) {
     77   return Runtime::Current()->GetStat(kind);
     78 }
     79 
     80 static void VMDebug_resetAllocCount(JNIEnv*, jclass, jint kinds) {
     81   Runtime::Current()->ResetStats(kinds);
     82 }
     83 
     84 static 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 
     91 static 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 
    116 static 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 
    128 static jint VMDebug_getMethodTracingMode(JNIEnv*, jclass) {
    129   return Trace::GetMethodTracingMode();
    130 }
    131 
    132 static void VMDebug_stopMethodTracing(JNIEnv*, jclass) {
    133   Trace::Stop();
    134 }
    135 
    136 static void VMDebug_startEmulatorTracing(JNIEnv*, jclass) {
    137   UNIMPLEMENTED(WARNING);
    138   // dvmEmulatorTraceStart();
    139 }
    140 
    141 static void VMDebug_stopEmulatorTracing(JNIEnv*, jclass) {
    142   UNIMPLEMENTED(WARNING);
    143   // dvmEmulatorTraceStop();
    144 }
    145 
    146 static jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) {
    147   return Dbg::IsDebuggerActive();
    148 }
    149 
    150 static jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) {
    151   return Dbg::IsJdwpConfigured();
    152 }
    153 
    154 static jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
    155   return Dbg::LastDebuggerActivity();
    156 }
    157 
    158 static void ThrowUnsupportedOperationException(JNIEnv* env) {
    159   ScopedObjectAccess soa(env);
    160   soa.Self()->ThrowNewException("Ljava/lang/UnsupportedOperationException;", nullptr);
    161 }
    162 
    163 static void VMDebug_startInstructionCounting(JNIEnv* env, jclass) {
    164   ThrowUnsupportedOperationException(env);
    165 }
    166 
    167 static void VMDebug_stopInstructionCounting(JNIEnv* env, jclass) {
    168   ThrowUnsupportedOperationException(env);
    169 }
    170 
    171 static void VMDebug_getInstructionCount(JNIEnv* env, jclass, jintArray /*javaCounts*/) {
    172   ThrowUnsupportedOperationException(env);
    173 }
    174 
    175 static void VMDebug_resetInstructionCount(JNIEnv* env, jclass) {
    176   ThrowUnsupportedOperationException(env);
    177 }
    178 
    179 static void VMDebug_printLoadedClasses(JNIEnv* env, jclass, jint flags) {
    180   ScopedFastNativeObjectAccess soa(env);
    181   return Runtime::Current()->GetClassLinker()->DumpAllClasses(flags);
    182 }
    183 
    184 static 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  */
    193 static 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  */
    203 static 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 
    235 static void VMDebug_dumpHprofDataDdms(JNIEnv*, jclass) {
    236   hprof::DumpHeap("[DDMS]", -1, true);
    237 }
    238 
    239 static 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 
    249 static void VMDebug_crash(JNIEnv*, jclass) {
    250   LOG(FATAL) << "Crashing runtime on request";
    251 }
    252 
    253 static void VMDebug_infopoint(JNIEnv*, jclass, jint id) {
    254   LOG(INFO) << "VMDebug infopoint " << id << " hit";
    255 }
    256 
    257 static 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 
    272 static 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.
    304 static 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().
    362 enum class VMDebugRuntimeStatId {
    363   kArtGcGcCount = 0,
    364   kArtGcGcTime,
    365   kArtGcBytesAllocated,
    366   kArtGcBytesFreed,
    367   kArtGcBlockingGcCount,
    368   kArtGcBlockingGcTime,
    369   kArtGcGcCountRateHistogram,
    370   kArtGcBlockingGcCountRateHistogram,
    371   kNumRuntimeStats,
    372 };
    373 
    374 static 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 
    416 static 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 
    426 static 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 
    478 static 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 
    513 void register_dalvik_system_VMDebug(JNIEnv* env) {
    514   REGISTER_NATIVE_METHODS("dalvik/system/VMDebug");
    515 }
    516 
    517 }  // namespace art
    518