1 /*
  2  * Copyright (c) 2016, 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 import java.lang.management.ManagementFactory;
 35 import java.util.LinkedList;
 36 import java.util.List;
 37 import java.util.Random;
 38 
 39 
 40 /**
 41  * @test TestUnifiedLoggingSwitchStress
 42  * @key gc stress
 43  * @summary Switches gc log level on fly while stressing memory/gc
 44  * @requires !vm.flightRecorder
 45  * @requires vm.gc != "Z"
 46  * @library /test/lib /
 47  * @modules java.management java.base/jdk.internal.misc
 48  *
 49  * @run main/othervm -Xmx256M -Xms256M
 50  *                   gc.logging.TestUnifiedLoggingSwitchStress 60
 51  */
 52 
 53 class MemoryStresser implements Runnable {
 54     public static volatile boolean shouldStop = false;
 55 
 56     private final List<byte[]> liveObjects = new LinkedList<>();
 57     private final List<byte[]> liveHObjects = new LinkedList<>();
 58     private int maxSimpleAllocationMemory = 0;
 59     private int usedMemory = 0;
 60 
 61     /**
 62      * Maximum amount of huge allocations
 63      */
 64     private static int H_ALLOCATION_MAX_COUNT = 4;
 65     /**
 66      * Maximum regions in one huge allocation
 67      */
 68     private static int H_ALLOCATION_REGION_SIZE = 2;
 69     private static final int G1_REGION_SIZE = 1024 * 1024;
 70     /**
 71      * Maximum size of simple allocation
 72      */
 73     private static final int MAX_SIMPLE_ALLOCATION_SIZE = (int) (G1_REGION_SIZE / 2 * 0.9);
 74 
 75     /**
 76      * Maximum size of dead (i.e. one which is made unreachable right after allocation) object
 77      */
 78     private static final int DEAD_OBJECT_MAX_SIZE = G1_REGION_SIZE / 10;
 79     private static final Random RND = Utils.getRandomInstance();
 80 
 81     /**
 82      * @param maxMemory maximum memory that could be allocated
 83      */
 84     public MemoryStresser(int maxMemory) {
 85         maxSimpleAllocationMemory = maxMemory - G1_REGION_SIZE * H_ALLOCATION_MAX_COUNT * H_ALLOCATION_REGION_SIZE;
 86     }
 87 
 88     public final Runnable[] actions = new Runnable[]{
 89             // Huge allocation
 90             () -> {
 91                 if (liveHObjects.size() < H_ALLOCATION_MAX_COUNT) {
 92                     int allocationSize = RND.nextInt((int) (G1_REGION_SIZE * (H_ALLOCATION_REGION_SIZE - 0.5)
 93                             * 0.9));
 94                     liveHObjects.add(new byte[allocationSize + G1_REGION_SIZE / 2]);
 95                 }
 96             },
 97 
 98             // Huge deallocation
 99             () -> {
100                 if (liveHObjects.size() > 0) {
101                     int elementNum = RND.nextInt(liveHObjects.size());
102                     liveHObjects.remove(elementNum);
103                 }
104             },
105 
106             // Simple allocation
107             () -> {
108                 if (maxSimpleAllocationMemory - usedMemory != 0) {
109                     int arraySize = RND.nextInt(Math.min(maxSimpleAllocationMemory - usedMemory,
110                             MAX_SIMPLE_ALLOCATION_SIZE));
111                     if (arraySize != 0) {
112                         liveObjects.add(new byte[arraySize]);
113                         usedMemory += arraySize;
114                     }
115                 }
116             },
117 
118             // Simple deallocation
119             () -> {
120                 if (liveObjects.size() != 0) {
121                     int elementNum = RND.nextInt(liveObjects.size());
122                     int shouldFree = liveObjects.get(elementNum).length;
123                     liveObjects.remove(elementNum);
124                     usedMemory -= shouldFree;
125                 }
126             },
127 
128             // Dead object allocation
129             () -> {
130                 int size = RND.nextInt(DEAD_OBJECT_MAX_SIZE);
131                 byte[] deadObject = new byte[size];
132             }
133     };
134 
135     @Override
136     public void run() {
137         while (!shouldStop) {
138             actions[RND.nextInt(actions.length)].run();
139             Thread.yield();
140         }
141 
142         System.out.println("Memory Stresser finished");
143     }
144 }
145 
146 class LogLevelSwitcher implements Runnable {
147 
148     public static volatile boolean shouldStop = false;
149     private final int logCount; // how many various log files will be used
150     private final String logFilePrefix; // name of log file will be logFilePrefix + index
151     private final Random RND = Utils.getRandomInstance();
152     private final MBeanServer MBS = ManagementFactory.getPlatformMBeanServer();
153 
154     /**
155      * @param logFilePrefix prefix for log files
156      * @param logCount     amount of log files
157      */
158     public LogLevelSwitcher(String logFilePrefix, int logCount) {
159         this.logCount = logCount;
160         this.logFilePrefix = logFilePrefix;
161 
162     }
163 
164     private static final String[] LOG_LEVELS = {"error", "warning", "info", "debug", "trace"};
165 
166     @Override
167     public void run() {
168 
169         while (!shouldStop) {
170             int fileNum = RND.nextInt(logCount);
171             int logLevel = RND.nextInt(LOG_LEVELS.length);
172 
173             String outputCommand = String.format("output=%s_%d.log", logFilePrefix, fileNum);
174             String logLevelCommand = "what='gc*=" + LOG_LEVELS[logLevel] + "'";
175 
176             try {
177                 Object out = MBS.invoke(new ObjectName("com.sun.management:type=DiagnosticCommand"),
178                                         "vmLog",
179                                         new Object[]{new String[]{outputCommand, logLevelCommand}},
180                                         new String[]{String[].class.getName()});
181 
182                 if (!out.toString().isEmpty()) {
183                     System.out.format("WARNING: Diagnostic command vmLog with arguments %s,%s returned not empty"
184                                     + " output %s\n",
185                             outputCommand, logLevelCommand, out);
186                 }
187             } catch (InstanceNotFoundException | MBeanException | ReflectionException | MalformedObjectNameException e) {
188                 System.out.println("Got exception trying to change log level:" + e);
189                 e.printStackTrace();
190                 throw new Error(e);
191             }
192             Thread.yield();
193         }
194         System.out.println("Log Switcher finished");
195     }
196 }
197 
198 
199 public class TestUnifiedLoggingSwitchStress {
200     /**
201      * Count of memory stressing threads
202      */
203     private static final int MEMORY_STRESSERS_COUNT = 3;
204     /**
205      * Count of log switching threads
206      */
207     private static final int LOG_LEVEL_SWITCHERS_COUNT = 2;
208     /**
209      * Count of log files created by each log switching thread
210      */
211     private static final int LOG_FILES_COUNT = 2;
212     /**
213      * Maximum amount memory allocated by each stressing thread
214      */
215     private static final int MAX_MEMORY_PER_STRESSER = (int) (Runtime.getRuntime().freeMemory()
216             / MEMORY_STRESSERS_COUNT * 0.7);
217 
218     public static void main(String[] args) throws InterruptedException {
219         if (args.length != 1) {
220             throw new Error("Test Bug: Expected duration (in seconds) wasn't provided as command line argument");
221         }
222         long duration = Integer.parseInt(args[0]) * 1000;
223 
224         long startTime = System.currentTimeMillis();
225 
226         List<Thread> threads = new LinkedList<>();
227 
228         for (int i = 0; i < LOG_LEVEL_SWITCHERS_COUNT; i++) {
229             threads.add(new Thread(new LogLevelSwitcher("Output_" + i, LOG_FILES_COUNT)));
230         }
231 
232         for (int i = 0; i < MEMORY_STRESSERS_COUNT; i++) {
233             threads.add(new Thread(new MemoryStresser(MAX_MEMORY_PER_STRESSER)));
234         }
235 
236         threads.stream().forEach(Thread::start);
237 
238         while (System.currentTimeMillis() - startTime < duration) {
239             Thread.yield();
240         }
241 
242         MemoryStresser.shouldStop = true;
243         LogLevelSwitcher.shouldStop = true;
244     }
245 }