1 /*
2  * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package gc.logging;
25 
26 import jdk.test.lib.Utils;
27 
28 import javax.management.InstanceNotFoundException;
29 import javax.management.MBeanException;
30 import javax.management.MBeanServer;
31 import javax.management.MalformedObjectNameException;
32 import javax.management.ObjectName;
33 import javax.management.ReflectionException;
34 
35 import static gc.testlibrary.Allocation.blackHole;
36 
37 import java.lang.management.ManagementFactory;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Random;
41 
42 
43 /**
44  * @test TestUnifiedLoggingSwitchStress
45  * @key stress randomness
46  * @summary Switches gc log level on fly while stressing memory/gc
47  * @requires !vm.flightRecorder
48  * @requires vm.gc != "Z"
49  * @library /test/lib /
50  * @modules java.management java.base/jdk.internal.misc
51  *
52  * @run main/othervm -Xmx256M -Xms256M
53  *                   gc.logging.TestUnifiedLoggingSwitchStress 60
54  */
55 
56 class MemoryStresser implements Runnable {
57     public static volatile boolean shouldStop = false;
58 
59     private final List<byte[]> liveObjects = new LinkedList<>();
60     private final List<byte[]> liveHObjects = new LinkedList<>();
61     private int maxSimpleAllocationMemory = 0;
62     private int usedMemory = 0;
63 
64     /**
65      * Maximum amount of huge allocations
66      */
67     private static int H_ALLOCATION_MAX_COUNT = 4;
68     /**
69      * Maximum regions in one huge allocation
70      */
71     private static int H_ALLOCATION_REGION_SIZE = 2;
72     private static final int G1_REGION_SIZE = 1024 * 1024;
73     /**
74      * Maximum size of simple allocation
75      */
76     private static final int MAX_SIMPLE_ALLOCATION_SIZE = (int) (G1_REGION_SIZE / 2 * 0.9);
77 
78     /**
79      * Maximum size of dead (i.e. one which is made unreachable right after allocation) object
80      */
81     private static final int DEAD_OBJECT_MAX_SIZE = G1_REGION_SIZE / 10;
82     private final Random rnd = new Random(Utils.getRandomInstance().nextLong());
83 
84     /**
85      * @param maxMemory maximum memory that could be allocated
86      */
MemoryStresser(int maxMemory)87     public MemoryStresser(int maxMemory) {
88         maxSimpleAllocationMemory = maxMemory - G1_REGION_SIZE * H_ALLOCATION_MAX_COUNT * H_ALLOCATION_REGION_SIZE;
89     }
90 
91     public final Runnable[] actions = new Runnable[]{
92             // Huge allocation
93             () -> {
94                 if (liveHObjects.size() < H_ALLOCATION_MAX_COUNT) {
95                     int allocationSize = rnd.nextInt((int) (G1_REGION_SIZE * (H_ALLOCATION_REGION_SIZE - 0.5)
96                             * 0.9));
97                     liveHObjects.add(new byte[allocationSize + G1_REGION_SIZE / 2]);
98                 }
99             },
100 
101             // Huge deallocation
102             () -> {
103                 if (liveHObjects.size() > 0) {
104                     int elementNum = rnd.nextInt(liveHObjects.size());
105                     liveHObjects.remove(elementNum);
106                 }
107             },
108 
109             // Simple allocation
110             () -> {
111                 if (maxSimpleAllocationMemory - usedMemory != 0) {
112                     int arraySize = rnd.nextInt(Math.min(maxSimpleAllocationMemory - usedMemory,
113                             MAX_SIMPLE_ALLOCATION_SIZE));
114                     if (arraySize != 0) {
115                         liveObjects.add(new byte[arraySize]);
116                         usedMemory += arraySize;
117                     }
118                 }
119             },
120 
121             // Simple deallocation
122             () -> {
123                 if (liveObjects.size() != 0) {
124                     int elementNum = rnd.nextInt(liveObjects.size());
125                     int shouldFree = liveObjects.get(elementNum).length;
126                     liveObjects.remove(elementNum);
127                     usedMemory -= shouldFree;
128                 }
129             },
130 
131             // Dead object allocation
132             () -> {
133                 int size = rnd.nextInt(DEAD_OBJECT_MAX_SIZE);
134                 blackHole(new byte[size]);
135             }
136     };
137 
138     @Override
run()139     public void run() {
140         while (!shouldStop) {
141             actions[rnd.nextInt(actions.length)].run();
142             Thread.yield();
143         }
144 
145         System.out.println("Memory Stresser finished");
146     }
147 }
148 
149 class LogLevelSwitcher implements Runnable {
150 
151     public static volatile boolean shouldStop = false;
152     private final int logCount; // how many various log files will be used
153     private final String logFilePrefix; // name of log file will be logFilePrefix + index
154     private final Random rnd;
155     private final MBeanServer MBS = ManagementFactory.getPlatformMBeanServer();
156 
157     /**
158      * @param logFilePrefix prefix for log files
159      * @param logCount     amount of log files
160      */
LogLevelSwitcher(String logFilePrefix, int logCount)161     public LogLevelSwitcher(String logFilePrefix, int logCount) {
162         this.logCount = logCount;
163         this.logFilePrefix = logFilePrefix;
164         this.rnd = new Random(Utils.getRandomInstance().nextLong());
165     }
166 
167     private static final String[] LOG_LEVELS = {"error", "warning", "info", "debug", "trace"};
168 
169     @Override
run()170     public void run() {
171 
172         while (!shouldStop) {
173             int fileNum = rnd.nextInt(logCount);
174             int logLevel = rnd.nextInt(LOG_LEVELS.length);
175 
176             String outputCommand = String.format("output=%s_%d.log", logFilePrefix, fileNum);
177             String logLevelCommand = "what='gc*=" + LOG_LEVELS[logLevel] + "'";
178 
179             try {
180                 Object out = MBS.invoke(new ObjectName("com.sun.management:type=DiagnosticCommand"),
181                                         "vmLog",
182                                         new Object[]{new String[]{outputCommand, logLevelCommand}},
183                                         new String[]{String[].class.getName()});
184 
185                 if (!out.toString().isEmpty()) {
186                     System.out.format("WARNING: Diagnostic command vmLog with arguments %s,%s returned not empty"
187                                     + " output %s\n",
188                             outputCommand, logLevelCommand, out);
189                 }
190             } catch (InstanceNotFoundException | MBeanException | ReflectionException | MalformedObjectNameException e) {
191                 System.out.println("Got exception trying to change log level:" + e);
192                 e.printStackTrace();
193                 throw new Error(e);
194             }
195             Thread.yield();
196         }
197         System.out.println("Log Switcher finished");
198     }
199 }
200 
201 
202 public class TestUnifiedLoggingSwitchStress {
203     /**
204      * Count of memory stressing threads
205      */
206     private static final int MEMORY_STRESSERS_COUNT = 3;
207     /**
208      * Count of log switching threads
209      */
210     private static final int LOG_LEVEL_SWITCHERS_COUNT = 2;
211     /**
212      * Count of log files created by each log switching thread
213      */
214     private static final int LOG_FILES_COUNT = 2;
215     /**
216      * Maximum amount memory allocated by each stressing thread
217      */
218     private static final int MAX_MEMORY_PER_STRESSER = (int) (Runtime.getRuntime().freeMemory()
219             / MEMORY_STRESSERS_COUNT * 0.7);
220 
main(String[] args)221     public static void main(String[] args) throws InterruptedException {
222         if (args.length != 1) {
223             throw new Error("Test Bug: Expected duration (in seconds) wasn't provided as command line argument");
224         }
225         long duration = Integer.parseInt(args[0]) * 1000;
226 
227         long startTime = System.currentTimeMillis();
228 
229         List<Thread> threads = new LinkedList<>();
230 
231         for (int i = 0; i < LOG_LEVEL_SWITCHERS_COUNT; i++) {
232             threads.add(new Thread(new LogLevelSwitcher("Output_" + i, LOG_FILES_COUNT)));
233         }
234 
235         for (int i = 0; i < MEMORY_STRESSERS_COUNT; i++) {
236             threads.add(new Thread(new MemoryStresser(MAX_MEMORY_PER_STRESSER)));
237         }
238 
239         threads.stream().forEach(Thread::start);
240 
241         while (System.currentTimeMillis() - startTime < duration) {
242             Thread.yield();
243         }
244 
245         MemoryStresser.shouldStop = true;
246         LogLevelSwitcher.shouldStop = true;
247     }
248 }
249