/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string.h>
#include <unistd.h>

#include "../class_linker.h"
#include "../common_throws.h"
#include "../debugger.h"
#include "../gc/space/dlmalloc_space.h"
#include "../gc/space/large_object_space.h"
#include "../gc/space/space-inl.h"
#include "../hprof/hprof.h"
#include "../jni_internal.h"
#include "../mirror/class.h"
#include "../../include/nativehelper/ScopedUtfChars.h"
#include "../scoped_thread_state_change.h"
#include "toStringArray.h"
#include "../trace.h"

namespace art {

static jobjectArray VMDebug_getVmFeatureList(JNIEnv* env, jclass) {
  std::vector<std::string> features;
  features.push_back("method-trace-profiling");
  features.push_back("method-trace-profiling-streaming");
  features.push_back("method-sample-profiling");
  features.push_back("hprof-heap-dump");
  features.push_back("hprof-heap-dump-streaming");
  return toStringArray(env, features);
}

static void VMDebug_startAllocCounting(JNIEnv*, jclass) {
  Runtime::Current()->SetStatsEnabled(true);
}

static void VMDebug_stopAllocCounting(JNIEnv*, jclass) {
  Runtime::Current()->SetStatsEnabled(false);
}

static jint VMDebug_getAllocCount(JNIEnv*, jclass, jint kind) {
  return Runtime::Current()->GetStat(kind);
}

static void VMDebug_resetAllocCount(JNIEnv*, jclass, jint kinds) {
  Runtime::Current()->ResetStats(kinds);
}

static void VMDebug_startMethodTracingDdmsImpl(JNIEnv*, jclass, jint bufferSize, jint flags,
                                               jboolean samplingEnabled, jint intervalUs) {
  Trace::Start("[DDMS]", -1, bufferSize, flags, true, samplingEnabled, intervalUs);
}

static void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceFilename,
                                         jobject javaFd, jint bufferSize, jint flags) {
  int originalFd = jniGetFDFromFileDescriptor(env, javaFd);
  if (originalFd < 0) {
    return;
  }

  int fd = dup(originalFd);
  if (fd < 0) {
    ScopedObjectAccess soa(env);
    ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
    soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/lang/RuntimeException;",
                                   "dup(%d) failed: %s", originalFd, strerror(errno));
    return;
  }

  ScopedUtfChars traceFilename(env, javaTraceFilename);
  if (traceFilename.c_str() == NULL) {
    return;
  }
  Trace::Start(traceFilename.c_str(), fd, bufferSize, flags, false, false, 0);
}

static void VMDebug_startMethodTracingFilename(JNIEnv* env, jclass, jstring javaTraceFilename,
                                               jint bufferSize, jint flags) {
  ScopedUtfChars traceFilename(env, javaTraceFilename);
  if (traceFilename.c_str() == NULL) {
    return;
  }
  Trace::Start(traceFilename.c_str(), -1, bufferSize, flags, false, false, 0);
}

static jint VMDebug_getMethodTracingMode(JNIEnv*, jclass) {
  return Trace::GetMethodTracingMode();
}

static void VMDebug_stopMethodTracing(JNIEnv*, jclass) {
  Trace::Stop();
}

static void VMDebug_startEmulatorTracing(JNIEnv*, jclass) {
  UNIMPLEMENTED(WARNING);
  // dvmEmulatorTraceStart();
}

static void VMDebug_stopEmulatorTracing(JNIEnv*, jclass) {
  UNIMPLEMENTED(WARNING);
  // dvmEmulatorTraceStop();
}

static jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) {
  return Dbg::IsDebuggerActive();
}

static jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) {
  return Dbg::IsJdwpConfigured();
}

static jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
  return Dbg::LastDebuggerActivity();
}

static void ThrowUnsupportedOperationException(JNIEnv* env) {
  ScopedObjectAccess soa(env);
  ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
  soa.Self()->ThrowNewException(throw_location, "Ljava/lang/UnsupportedOperationException;", NULL);
}

static void VMDebug_startInstructionCounting(JNIEnv* env, jclass) {
  ThrowUnsupportedOperationException(env);
}

static void VMDebug_stopInstructionCounting(JNIEnv* env, jclass) {
  ThrowUnsupportedOperationException(env);
}

static void VMDebug_getInstructionCount(JNIEnv* env, jclass, jintArray /*javaCounts*/) {
  ThrowUnsupportedOperationException(env);
}

static void VMDebug_resetInstructionCount(JNIEnv* env, jclass) {
  ThrowUnsupportedOperationException(env);
}

static void VMDebug_printLoadedClasses(JNIEnv* env, jclass, jint flags) {
  ScopedObjectAccess soa(env);
  return Runtime::Current()->GetClassLinker()->DumpAllClasses(flags);
}

static jint VMDebug_getLoadedClassCount(JNIEnv* env, jclass) {
  ScopedObjectAccess soa(env);
  return Runtime::Current()->GetClassLinker()->NumLoadedClasses();
}

/*
 * Returns the thread-specific CPU-time clock value for the current thread,
 * or -1 if the feature isn't supported.
 */
static jlong VMDebug_threadCpuTimeNanos(JNIEnv*, jclass) {
  return ThreadCpuNanoTime();
}

/*
 * static void dumpHprofData(String fileName, FileDescriptor fd)
 *
 * Cause "hprof" data to be dumped.  We can throw an IOException if an
 * error occurs during file handling.
 */
static void VMDebug_dumpHprofData(JNIEnv* env, jclass, jstring javaFilename, jobject javaFd) {
  // Only one of these may be NULL.
  if (javaFilename == NULL && javaFd == NULL) {
    ScopedObjectAccess soa(env);
    ThrowNullPointerException(NULL, "fileName == null && fd == null");
    return;
  }

  std::string filename;
  if (javaFilename != NULL) {
    ScopedUtfChars chars(env, javaFilename);
    if (env->ExceptionCheck()) {
      return;
    }
    filename = chars.c_str();
  } else {
    filename = "[fd]";
  }

  int fd = -1;
  if (javaFd != NULL) {
    fd = jniGetFDFromFileDescriptor(env, javaFd);
    if (fd < 0) {
      ScopedObjectAccess soa(env);
      ThrowRuntimeException("Invalid file descriptor");
      return;
    }
  }

  hprof::DumpHeap(filename.c_str(), fd, false);
}

static void VMDebug_dumpHprofDataDdms(JNIEnv*, jclass) {
  hprof::DumpHeap("[DDMS]", -1, true);
}

static void VMDebug_dumpReferenceTables(JNIEnv* env, jclass) {
  ScopedObjectAccess soa(env);
  LOG(INFO) << "--- reference table dump ---";

  soa.Env()->DumpReferenceTables(LOG(INFO));
  soa.Vm()->DumpReferenceTables(LOG(INFO));

  LOG(INFO) << "---";
}

static void VMDebug_crash(JNIEnv*, jclass) {
  LOG(FATAL) << "Crashing runtime on request";
}

static void VMDebug_infopoint(JNIEnv*, jclass, jint id) {
  LOG(INFO) << "VMDebug infopoint " << id << " hit";
}

static jlong VMDebug_countInstancesOfClass(JNIEnv* env, jclass, jclass javaClass,
                                           jboolean countAssignable) {
  ScopedObjectAccess soa(env);
  mirror::Class* c = soa.Decode<mirror::Class*>(javaClass);
  if (c == NULL) {
    return 0;
  }
  std::vector<mirror::Class*> classes;
  classes.push_back(c);
  uint64_t count = 0;
  Runtime::Current()->GetHeap()->CountInstances(classes, countAssignable, &count);
  return count;
}

// We export the VM internal per-heap-space size/alloc/free metrics
// for the zygote space, alloc space (application heap), and the large
// object space for dumpsys meminfo. The other memory region data such
// as PSS, private/shared dirty/shared data are available via
// /proc/<pid>/smaps.
static void VMDebug_getHeapSpaceStats(JNIEnv* env, jclass, jlongArray data) {
  jlong* arr = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(data, 0));
  if (arr == NULL || env->GetArrayLength(data) < 9) {
    return;
  }

  size_t allocSize = 0;
  size_t allocUsed = 0;
  size_t zygoteSize = 0;
  size_t zygoteUsed = 0;
  size_t largeObjectsSize = 0;
  size_t largeObjectsUsed = 0;

  gc::Heap* heap = Runtime::Current()->GetHeap();
  const std::vector<gc::space::ContinuousSpace*>& continuous_spaces = heap->GetContinuousSpaces();
  const std::vector<gc::space::DiscontinuousSpace*>& discontinuous_spaces = heap->GetDiscontinuousSpaces();
  typedef std::vector<gc::space::ContinuousSpace*>::const_iterator It;
  for (It it = continuous_spaces.begin(), end = continuous_spaces.end(); it != end; ++it) {
    gc::space::ContinuousSpace* space = *it;
    if (space->IsImageSpace()) {
      // Currently don't include the image space.
    } else if (space->IsZygoteSpace()) {
      gc::space::DlMallocSpace* dlmalloc_space = space->AsDlMallocSpace();
      zygoteSize += dlmalloc_space->GetFootprint();
      zygoteUsed += dlmalloc_space->GetBytesAllocated();
    } else {
      // This is the alloc space.
      gc::space::DlMallocSpace* dlmalloc_space = space->AsDlMallocSpace();
      allocSize += dlmalloc_space->GetFootprint();
      allocUsed += dlmalloc_space->GetBytesAllocated();
    }
  }
  typedef std::vector<gc::space::DiscontinuousSpace*>::const_iterator It2;
  for (It2 it = discontinuous_spaces.begin(), end = discontinuous_spaces.end(); it != end; ++it) {
    gc::space::DiscontinuousSpace* space = *it;
    if (space->IsLargeObjectSpace()) {
      largeObjectsSize += space->AsLargeObjectSpace()->GetBytesAllocated();
      largeObjectsUsed += largeObjectsSize;
    }
  }

  size_t allocFree = allocSize - allocUsed;
  size_t zygoteFree = zygoteSize - zygoteUsed;
  size_t largeObjectsFree = largeObjectsSize - largeObjectsUsed;

  int j = 0;
  arr[j++] = allocSize;
  arr[j++] = allocUsed;
  arr[j++] = allocFree;
  arr[j++] = zygoteSize;
  arr[j++] = zygoteUsed;
  arr[j++] = zygoteFree;
  arr[j++] = largeObjectsSize;
  arr[j++] = largeObjectsUsed;
  arr[j++] = largeObjectsFree;
  env->ReleasePrimitiveArrayCritical(data, arr, 0);
}

static JNINativeMethod gMethods[] = {
  NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
  NATIVE_METHOD(VMDebug, crash, "()V"),
  NATIVE_METHOD(VMDebug, dumpHprofData, "(Ljava/lang/String;Ljava/io/FileDescriptor;)V"),
  NATIVE_METHOD(VMDebug, dumpHprofDataDdms, "()V"),
  NATIVE_METHOD(VMDebug, dumpReferenceTables, "()V"),
  NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
  NATIVE_METHOD(VMDebug, getHeapSpaceStats, "([J)V"),
  NATIVE_METHOD(VMDebug, getInstructionCount, "([I)V"),
  NATIVE_METHOD(VMDebug, getLoadedClassCount, "()I"),
  NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
  NATIVE_METHOD(VMDebug, infopoint, "(I)V"),
  NATIVE_METHOD(VMDebug, isDebuggerConnected, "()Z"),
  NATIVE_METHOD(VMDebug, isDebuggingEnabled, "()Z"),
  NATIVE_METHOD(VMDebug, getMethodTracingMode, "()I"),
  NATIVE_METHOD(VMDebug, lastDebuggerActivity, "()J"),
  NATIVE_METHOD(VMDebug, printLoadedClasses, "(I)V"),
  NATIVE_METHOD(VMDebug, resetAllocCount, "(I)V"),
  NATIVE_METHOD(VMDebug, resetInstructionCount, "()V"),
  NATIVE_METHOD(VMDebug, startAllocCounting, "()V"),
  NATIVE_METHOD(VMDebug, startEmulatorTracing, "()V"),
  NATIVE_METHOD(VMDebug, startInstructionCounting, "()V"),
  NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(IIZI)V"),
  NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;II)V"),
  NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;II)V"),
  NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"),
  NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"),
  NATIVE_METHOD(VMDebug, stopInstructionCounting, "()V"),
  NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
  NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "()J"),
};

void register_dalvik_system_VMDebug(JNIEnv* env) {
  REGISTER_NATIVE_METHODS("dalvik/system/VMDebug");
}

}  // namespace art
