/*
 * Copyright (c) 2023 Red Hat Inc. All rights reserved.
 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "precompiled.hpp"
#include "runtime/os.hpp"
#include "runtime/trimNativeHeap.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/ostream.hpp"
#include "testutils.hpp"
#include "unittest.hpp"

using ::testing::HasSubstr;

// Check the state of the trimmer via print_state; returns the suspend count
static int check_trim_state() {
  char buf [1024];
  stringStream ss(buf, sizeof(buf));
  NativeHeapTrimmer::print_state(&ss);
  if (NativeHeapTrimmer::enabled()) {
    assert(TrimNativeHeapInterval > 0, "Sanity");
    EXPECT_THAT(buf, HasSubstr("Periodic native trim enabled"));

    const char* s = ::strstr(buf, "Trims performed");
    EXPECT_NOT_NULL(s);

    uint64_t num_trims = 0;
    int suspend_count = 0;
    int stopped = 0;
    EXPECT_EQ(::sscanf(s, "Trims performed: " UINT64_FORMAT ", current suspend count: %d, stopped: %d",
                       &num_trims, &suspend_count, &stopped), 3);

    // Number of trims we can reasonably expect should be limited
    const double fudge_factor = 1.5;
    const uint64_t elapsed_ms = (uint64_t)(os::elapsedTime() * fudge_factor * 1000.0);
    const uint64_t max_num_trims = (elapsed_ms / TrimNativeHeapInterval) + 1;
    EXPECT_LE(num_trims, max_num_trims);

    // We should not be stopped
    EXPECT_EQ(stopped, 0);

    // Suspend count must not underflow
    EXPECT_GE(suspend_count, 0);
    return suspend_count;

  } else {
    EXPECT_THAT(buf, HasSubstr("Periodic native trim disabled"));
    EXPECT_THAT(buf, Not(HasSubstr("Trims performed")));
    return 0;
  }
}

TEST_VM(os, TrimNative) {

  if (!NativeHeapTrimmer::enabled()) {
    return;
  }

  // Try recursive pausing. This tests that we are able to pause, that pauses stack,
  // and that stacking works within the same thread.
  int c1 = 0, c2 = 0, c3 = 0;
  {
    NativeHeapTrimmer::SuspendMark sm1("Test1");
    c1 = check_trim_state();
    {
      NativeHeapTrimmer::SuspendMark sm2("Test2");
      c2 = check_trim_state();
      {
        NativeHeapTrimmer::SuspendMark sm3("Test3");
        c3 = check_trim_state();
      }
    }
  }
  // We also check the state: the suspend count should go up. But since we don't know
  // whether concurrent code will have increased the suspend count too, this is fuzzy and
  // we must avoid intermittent false positives.
  EXPECT_GT(c2, c1);
  EXPECT_GT(c3, c2);
}