/*
 * Copyright (C) 2014 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 "base/macros.h"
#include "jni.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"

#include "com_android_libcore.h"
#include <errno.h>
#include <sstream>
#include <string.h>
#include <sys/resource.h>

namespace art {

extern "C" JNIEXPORT jint JNICALL Java_Main_getNativePriority(JNIEnv* env,
                                                              [[maybe_unused]] jclass clazz) {
  return Thread::ForEnv(env)->GetNativePriority();
}


extern "C" JNIEXPORT jint JNICALL Java_Main_haveNicenessApis([[maybe_unused]] JNIEnv* env,
                                                             [[maybe_unused]] jclass clazz) {
  if (com::android::libcore::niceness_apis()) {
    return JNI_TRUE;
  } else {
    return JNI_FALSE;
  }
}

extern "C" JNIEXPORT jboolean JNICALL Java_Main_supportsThreadPriorities(
    [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) {
  int my_niceness = getpriority(PRIO_PROCESS, 0 /* self */);
  int my_priority = Thread::NicenessToPriority(my_niceness);
  int new_priority = (my_priority == kMaxThreadPriority ? my_priority - 2 : my_priority + 1);
  {
    ScopedObjectAccess soa(env);
    Thread::Current()->SetNativePriority(new_priority);
  }
  int new_niceness = getpriority(PRIO_PROCESS, 0 /* self */);
  {
    ScopedObjectAccess soa(env);
    Thread::Current()->SetNativePriority(my_priority);
  }
  if (new_niceness == my_niceness) {
    // Had no effect.
    return JNI_FALSE;
  } else {
    // Assume it did the right thing.
    return JNI_TRUE;
  }
}

// Returns a description of Java to Posix priority mapping, and information about how we obtained
// it. Used only for test failure reporting, and hence a bit sloppy in terms of error checks.
extern "C" JNIEXPORT jstring JNICALL Java_Main_getPriorityInfo(JNIEnv* env,
                                                               [[maybe_unused]] jclass clazz) {
  std::ostringstream result;

  result << "Java priorities mapping: ";
  for (int p = kMinThreadPriority; p <= kMaxThreadPriority; ++p) {
    if (p != kMinThreadPriority) {
      result << ", ";
    }
    result << p << ": " << Thread::PriorityToNiceness(p);
  }
  result << ";\nSetpriority effects: ";
  int my_niceness = getpriority(PRIO_PROCESS, 0 /* self */);
  for (int p = -20; p <= 19; p += 3) {
    if (p != -20) {
      result << ", ";
    }
    int ret = setpriority(PRIO_PROCESS, 0 /* self */, p);
    result << p << ": ";
    if (ret == 0) {
      result << getpriority(PRIO_PROCESS, 0 /* self */);
    } else {
      result << "failed:" << strerror(errno);
    }
  }
  // Test whether SetNativePriority has any impact. If not, that suggests that either setpriority
  // doesn't work in this range, or we've decided that setpriority cannot be relied upon for other
  // reasons. See `canSetPriority` in thread.cc. Intentionally slightly different from
  // supportsThreadPriorities test, so that we have a chance of detecting inconsistencies.
  {
    ScopedObjectAccess soa(env);
    Thread::Current()->SetNativePriority(6);
  }
  if (getpriority(PRIO_PROCESS, 0 /* self */) != Thread::PriorityToNiceness(6)) {
    result << ";\nPriority setting not working";
  }
  result << ";\nSDK version is " << android::base::GetIntProperty("ro.build.version.sdk", 0);
  // Restore priority to something plausible, if possible.
  setpriority(PRIO_PROCESS, 0 /* self */, my_niceness);
  return env->NewStringUTF(result.str().c_str());
}

}  // namespace art
