提交 fa971ab4 编写于 作者: C ctornqvi

8009125: Add NMT tests for Virtual Memory operations

Summary: Tests added for Reserve/Commit/Uncommit/Unreserve operations
Reviewed-by: zgu, mgerdin
上级 8b303da7
......@@ -118,45 +118,46 @@ WB_END
#endif // INCLUDE_ALL_GCS
#ifdef INCLUDE_NMT
// Keep track of the 3 allocations in NMTAllocTest so we can free them later
// on and verify that they're not visible anymore
static void* nmtMtTest1 = NULL, *nmtMtTest2 = NULL, *nmtMtTest3 = NULL;
// Alloc memory using the test memory type so that we can use that to see if
// NMT picks it up correctly
WB_ENTRY(jboolean, WB_NMTAllocTest(JNIEnv* env))
void *mem;
WB_ENTRY(jlong, WB_NMTMalloc(JNIEnv* env, jobject o, jlong size))
jlong addr = 0;
if (!MemTracker::is_on() || MemTracker::shutdown_in_progress()) {
return false;
if (MemTracker::is_on() && !MemTracker::shutdown_in_progress()) {
addr = (jlong)(uintptr_t)os::malloc(size, mtTest);
}
// Allocate 2 * 128k + 256k + 1024k and free the 1024k one to make sure we track
// everything correctly. Total should be 512k held alive.
nmtMtTest1 = os::malloc(128 * 1024, mtTest);
mem = os::malloc(1024 * 1024, mtTest);
nmtMtTest2 = os::malloc(256 * 1024, mtTest);
os::free(mem, mtTest);
nmtMtTest3 = os::malloc(128 * 1024, mtTest);
return true;
return addr;
WB_END
// Free the memory allocated by NMTAllocTest
WB_ENTRY(jboolean, WB_NMTFreeTestMemory(JNIEnv* env))
WB_ENTRY(void, WB_NMTFree(JNIEnv* env, jobject o, jlong mem))
os::free((void*)(uintptr_t)mem, mtTest);
WB_END
if (nmtMtTest1 == NULL || nmtMtTest2 == NULL || nmtMtTest3 == NULL) {
return false;
WB_ENTRY(jlong, WB_NMTReserveMemory(JNIEnv* env, jobject o, jlong size))
jlong addr = 0;
if (MemTracker::is_on() && !MemTracker::shutdown_in_progress()) {
addr = (jlong)(uintptr_t)os::reserve_memory(size);
MemTracker::record_virtual_memory_type((address)addr, mtTest);
}
os::free(nmtMtTest1, mtTest);
nmtMtTest1 = NULL;
os::free(nmtMtTest2, mtTest);
nmtMtTest2 = NULL;
os::free(nmtMtTest3, mtTest);
nmtMtTest3 = NULL;
return addr;
WB_END
return true;
WB_ENTRY(void, WB_NMTCommitMemory(JNIEnv* env, jobject o, jlong addr, jlong size))
os::commit_memory((char *)(uintptr_t)addr, size);
MemTracker::record_virtual_memory_type((address)(uintptr_t)addr, mtTest);
WB_END
WB_ENTRY(void, WB_NMTUncommitMemory(JNIEnv* env, jobject o, jlong addr, jlong size))
os::uncommit_memory((char *)(uintptr_t)addr, size);
WB_END
WB_ENTRY(void, WB_NMTReleaseMemory(JNIEnv* env, jobject o, jlong addr, jlong size))
os::release_memory((char *)(uintptr_t)addr, size);
WB_END
// Block until the current generation of NMT data to be merged, used to reliably test the NMT feature
......@@ -340,9 +341,13 @@ static JNINativeMethod methods[] = {
{CC"g1RegionSize", CC"()I", (void*)&WB_G1RegionSize },
#endif // INCLUDE_ALL_GCS
#ifdef INCLUDE_NMT
{CC"NMTAllocTest", CC"()Z", (void*)&WB_NMTAllocTest },
{CC"NMTFreeTestMemory", CC"()Z", (void*)&WB_NMTFreeTestMemory },
{CC"NMTWaitForDataMerge",CC"()Z", (void*)&WB_NMTWaitForDataMerge},
{CC"NMTMalloc", CC"(J)J", (void*)&WB_NMTMalloc },
{CC"NMTFree", CC"(J)V", (void*)&WB_NMTFree },
{CC"NMTReserveMemory", CC"(J)J", (void*)&WB_NMTReserveMemory },
{CC"NMTCommitMemory", CC"(JJ)V", (void*)&WB_NMTCommitMemory },
{CC"NMTUncommitMemory", CC"(JJ)V", (void*)&WB_NMTUncommitMemory },
{CC"NMTReleaseMemory", CC"(JJ)V", (void*)&WB_NMTReleaseMemory },
{CC"NMTWaitForDataMerge", CC"()Z", (void*)&WB_NMTWaitForDataMerge},
#endif // INCLUDE_NMT
{CC"deoptimizeAll", CC"()V", (void*)&WB_DeoptimizeAll },
{CC"deoptimizeMethod", CC"(Ljava/lang/reflect/Method;)I",
......
......@@ -26,30 +26,33 @@
* @summary Test consistency of NMT by leaking a few select allocations of the Test type and then verify visibility with jcmd
* @key nmt jcmd
* @library /testlibrary /testlibrary/whitebox
* @build AllocTestType
* @build MallocTestType
* @run main ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail AllocTestType
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail MallocTestType
*/
import com.oracle.java.testlibrary.*;
import sun.hotspot.WhiteBox;
public class AllocTestType {
public class MallocTestType {
public static void main(String args[]) throws Exception {
OutputAnalyzer output;
WhiteBox wb = WhiteBox.getWhiteBox();
// Grab my own PID
String pid = Integer.toString(ProcessTools.getProcessId());
ProcessBuilder pb = new ProcessBuilder();
// Use WB API to alloc with the mtTest type
if (!WhiteBox.getWhiteBox().NMTAllocTest()) {
throw new Exception("Call to WB API NMTAllocTest() failed");
}
// Use WB API to alloc and free with the mtTest type
long memAlloc3 = wb.NMTMalloc(128 * 1024);
long memAlloc2 = wb.NMTMalloc(256 * 1024);
wb.NMTFree(memAlloc3);
long memAlloc1 = wb.NMTMalloc(512 * 1024);
wb.NMTFree(memAlloc2);
// Use WB API to ensure that all data has been merged before we continue
if (!WhiteBox.getWhiteBox().NMTWaitForDataMerge()) {
if (!wb.NMTWaitForDataMerge()) {
throw new Exception("Call to WB API NMTWaitForDataMerge() failed");
}
......@@ -59,12 +62,10 @@ public class AllocTestType {
output.shouldContain("Test (reserved=512KB, committed=512KB)");
// Free the memory allocated by NMTAllocTest
if (!WhiteBox.getWhiteBox().NMTFreeTestMemory()) {
throw new Exception("Call to WB API NMTFreeTestMemory() failed");
}
wb.NMTFree(memAlloc1);
// Use WB API to ensure that all data has been merged before we continue
if (!WhiteBox.getWhiteBox().NMTWaitForDataMerge()) {
if (!wb.NMTWaitForDataMerge()) {
throw new Exception("Call to WB API NMTWaitForDataMerge() failed");
}
output = new OutputAnalyzer(pb.start());
......
/*
* Copyright (c) 2013, 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.
*/
/*
* @test
* @key nmt jcmd
* @library /testlibrary /testlibrary/whitebox
* @build ThreadedMallocTestType
* @run main ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail ThreadedMallocTestType
*/
import com.oracle.java.testlibrary.*;
import sun.hotspot.WhiteBox;
public class ThreadedMallocTestType {
public static long memAlloc1;
public static long memAlloc2;
public static long memAlloc3;
public static void main(String args[]) throws Exception {
OutputAnalyzer output;
final WhiteBox wb = WhiteBox.getWhiteBox();
// Grab my own PID
String pid = Integer.toString(ProcessTools.getProcessId());
ProcessBuilder pb = new ProcessBuilder();
Thread allocThread = new Thread() {
public void run() {
// Alloc memory using the WB api
memAlloc1 = wb.NMTMalloc(128 * 1024);
memAlloc2 = wb.NMTMalloc(256 * 1024);
memAlloc3 = wb.NMTMalloc(512 * 1024);
}
};
allocThread.start();
allocThread.join();
// Use WB API to ensure that all data has been merged before we continue
if (!wb.NMTWaitForDataMerge()) {
throw new Exception("Call to WB API NMTWaitForDataMerge() failed");
}
// Run 'jcmd <pid> VM.native_memory summary'
pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "summary"});
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=896KB, committed=896KB)");
Thread freeThread = new Thread() {
public void run() {
// Free the memory allocated by NMTMalloc
wb.NMTFree(memAlloc1);
wb.NMTFree(memAlloc2);
wb.NMTFree(memAlloc3);
}
};
freeThread.start();
freeThread.join();
// Use WB API to ensure that all data has been merged before we continue
if (!wb.NMTWaitForDataMerge()) {
throw new Exception("Call to WB API NMTWaitForDataMerge() failed");
}
output = new OutputAnalyzer(pb.start());
output.shouldNotContain("Test (reserved=");
}
}
/*
* Copyright (c) 2013, 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.
*/
/*
* @test
* @key nmt jcmd
* @library /testlibrary /testlibrary/whitebox
* @build ThreadedVirtualAllocTestType
* @run main ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail ThreadedVirtualAllocTestType
*/
import com.oracle.java.testlibrary.*;
import sun.hotspot.WhiteBox;
public class ThreadedVirtualAllocTestType {
public static long addr;
public static final WhiteBox wb = WhiteBox.getWhiteBox();
public static final long commitSize = 128 * 1024;
public static final long reserveSize = 512 * 1024;
public static void main(String args[]) throws Exception {
OutputAnalyzer output;
String pid = Integer.toString(ProcessTools.getProcessId());
ProcessBuilder pb = new ProcessBuilder();
Thread reserveThread = new Thread() {
public void run() {
addr = wb.NMTReserveMemory(reserveSize);
}
};
reserveThread.start();
reserveThread.join();
mergeData();
pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "detail"});
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=512KB, committed=0KB)");
output.shouldMatch("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + reserveSize) + "\\] reserved 512KB for Test");
Thread commitThread = new Thread() {
public void run() {
wb.NMTCommitMemory(addr, commitSize);
}
};
commitThread.start();
commitThread.join();
mergeData();
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=512KB, committed=128KB)");
output.shouldMatch("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + commitSize) + "\\] committed 128KB");
Thread uncommitThread = new Thread() {
public void run() {
wb.NMTUncommitMemory(addr, commitSize);
}
};
uncommitThread.start();
uncommitThread.join();
mergeData();
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=512KB, committed=0KB)");
output.shouldNotMatch("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + commitSize) + "\\] committed");
Thread releaseThread = new Thread() {
public void run() {
wb.NMTReleaseMemory(addr, reserveSize);
}
};
releaseThread.start();
releaseThread.join();
mergeData();
output = new OutputAnalyzer(pb.start());
output.shouldNotContain("Test (reserved=");
output.shouldNotContain("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + reserveSize) + "\\] reserved");
}
public static void mergeData() throws Exception {
// Use WB API to ensure that all data has been merged before we continue
if (!wb.NMTWaitForDataMerge()) {
throw new Exception("Call to WB API NMTWaitForDataMerge() failed");
}
}
}
/*
* Copyright (c) 2013, 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.
*/
/*
* @test
* @summary Test Reserve/Commit/Uncommit/Release of virtual memory and that we track it correctly
* @key nmt jcmd
* @library /testlibrary /testlibrary/whitebox
* @build VirtualAllocTestType
* @run main ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail VirtualAllocTestType
*/
import com.oracle.java.testlibrary.*;
import sun.hotspot.WhiteBox;
public class VirtualAllocTestType {
public static WhiteBox wb = WhiteBox.getWhiteBox();
public static void main(String args[]) throws Exception {
OutputAnalyzer output;
long commitSize = 128 * 1024;
long reserveSize = 256 * 1024;
long addr;
String pid = Integer.toString(ProcessTools.getProcessId());
ProcessBuilder pb = new ProcessBuilder();
addr = wb.NMTReserveMemory(reserveSize);
mergeData();
pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "detail"});
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=256KB, committed=0KB)");
output.shouldMatch("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + reserveSize) + "\\] reserved 256KB for Test");
wb.NMTCommitMemory(addr, commitSize);
mergeData();
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=256KB, committed=128KB)");
output.shouldMatch("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + commitSize) + "\\] committed 128KB");
wb.NMTUncommitMemory(addr, commitSize);
mergeData();
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=256KB, committed=0KB)");
output.shouldNotMatch("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + commitSize) + "\\] committed");
wb.NMTReleaseMemory(addr, reserveSize);
mergeData();
output = new OutputAnalyzer(pb.start());
output.shouldNotContain("Test (reserved=");
output.shouldNotMatch("\\[0x[0]*" + Long.toHexString(addr) + " - 0x[0]*" + Long.toHexString(addr + reserveSize) + "\\] reserved");
}
public static void mergeData() throws Exception {
// Use WB API to ensure that all data has been merged before we continue
if (!wb.NMTWaitForDataMerge()) {
throw new Exception("Call to WB API NMTWaitForDataMerge() failed");
}
}
}
......@@ -36,6 +36,11 @@ public class OutputAnalyzerTest {
String stdout = "aaaaaa";
String stderr = "bbbbbb";
// Regexps used for testing pattern matching of the test input
String stdoutPattern = "[a]";
String stderrPattern = "[b]";
String nonExistingPattern = "[c]";
OutputAnalyzer output = new OutputAnalyzer(stdout, stderr);
if (!stdout.equals(output.getStdout())) {
......@@ -104,5 +109,68 @@ public class OutputAnalyzerTest {
} catch (RuntimeException e) {
// expected
}
// Should match
try {
output.shouldMatch(stdoutPattern);
output.stdoutShouldMatch(stdoutPattern);
output.shouldMatch(stderrPattern);
output.stderrShouldMatch(stderrPattern);
} catch (RuntimeException e) {
throw new Exception("shouldMatch() failed", e);
}
try {
output.shouldMatch(nonExistingPattern);
throw new Exception("shouldMatch() failed to throw exception");
} catch (RuntimeException e) {
// expected
}
try {
output.stdoutShouldMatch(stderrPattern);
throw new Exception(
"stdoutShouldMatch() failed to throw exception");
} catch (RuntimeException e) {
// expected
}
try {
output.stderrShouldMatch(stdoutPattern);
throw new Exception(
"stderrShouldMatch() failed to throw exception");
} catch (RuntimeException e) {
// expected
}
// Should not match
try {
output.shouldNotMatch(nonExistingPattern);
output.stdoutShouldNotMatch(nonExistingPattern);
output.stderrShouldNotMatch(nonExistingPattern);
} catch (RuntimeException e) {
throw new Exception("shouldNotMatch() failed", e);
}
try {
output.shouldNotMatch(stdoutPattern);
throw new Exception("shouldNotMatch() failed to throw exception");
} catch (RuntimeException e) {
// expected
}
try {
output.stdoutShouldNotMatch(stdoutPattern);
throw new Exception("shouldNotMatch() failed to throw exception");
} catch (RuntimeException e) {
// expected
}
try {
output.stderrShouldNotMatch(stderrPattern);
throw new Exception("shouldNotMatch() failed to throw exception");
} catch (RuntimeException e) {
// expected
}
}
}
......@@ -24,6 +24,8 @@
package com.oracle.java.testlibrary;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class OutputAnalyzer {
......@@ -141,6 +143,103 @@ public final class OutputAnalyzer {
}
}
/**
* Verify that the stdout and stderr contents of output buffer matches
* the pattern
*
* @param pattern
* @throws RuntimeException If the pattern was not found
*/
public void shouldMatch(String pattern) {
Matcher stdoutMatcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
Matcher stderrMatcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
if (!stdoutMatcher.find() && !stderrMatcher.find()) {
throw new RuntimeException("'" + pattern
+ "' missing from stdout/stderr: [" + stdout + stderr
+ "]\n");
}
}
/**
* Verify that the stdout contents of output buffer matches the
* pattern
*
* @param pattern
* @throws RuntimeException If the pattern was not found
*/
public void stdoutShouldMatch(String pattern) {
Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
if (!matcher.find()) {
throw new RuntimeException("'" + pattern
+ "' missing from stdout: [" + stdout + "]\n");
}
}
/**
* Verify that the stderr contents of output buffer matches the
* pattern
*
* @param pattern
* @throws RuntimeException If the pattern was not found
*/
public void stderrShouldMatch(String pattern) {
Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
if (!matcher.find()) {
throw new RuntimeException("'" + pattern
+ "' missing from stderr: [" + stderr + "]\n");
}
}
/**
* Verify that the stdout and stderr contents of output buffer does not
* match the pattern
*
* @param pattern
* @throws RuntimeException If the pattern was found
*/
public void shouldNotMatch(String pattern) {
Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
if (matcher.find()) {
throw new RuntimeException("'" + pattern
+ "' found in stdout: [" + stdout + "]\n");
}
matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
if (matcher.find()) {
throw new RuntimeException("'" + pattern
+ "' found in stderr: [" + stderr + "]\n");
}
}
/**
* Verify that the stdout contents of output buffer does not match the
* pattern
*
* @param pattern
* @throws RuntimeException If the pattern was found
*/
public void stdoutShouldNotMatch(String pattern) {
Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stdout);
if (matcher.find()) {
throw new RuntimeException("'" + pattern
+ "' found in stdout: [" + stdout + "]\n");
}
}
/**
* Verify that the stderr contents of output buffer does not match the
* pattern
*
* @param pattern
* @throws RuntimeException If the pattern was found
*/
public void stderrShouldNotMatch(String pattern) {
Matcher matcher = Pattern.compile(pattern, Pattern.MULTILINE).matcher(stderr);
if (matcher.find()) {
throw new RuntimeException("'" + pattern
+ "' found in stderr: [" + stderr + "]\n");
}
}
/**
* Verifiy the exit value of the process
*
......
......@@ -80,8 +80,12 @@ public class WhiteBox {
public native Object[] parseCommandLine(String commandline, DiagnosticCommand[] args);
// NMT
public native boolean NMTAllocTest();
public native boolean NMTFreeTestMemory();
public native long NMTMalloc(long size);
public native void NMTFree(long mem);
public native long NMTReserveMemory(long size);
public native void NMTCommitMemory(long addr, long size);
public native void NMTUncommitMemory(long addr, long size);
public native void NMTReleaseMemory(long addr, long size);
public native boolean NMTWaitForDataMerge();
// Compiler
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册