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                 @SuppressWarnings("unused")
132                 byte[] deadObject = new byte[size];
133             }
134     };
135 
136     @Override
137     public void run() {
138         while (!shouldStop) {
139             actions[RND.nextInt(actions.length)].run();
140             Thread.yield();
141         }
142 
143         System.out.println("Memory Stresser finished");
144     }
145 }
146 
147 class LogLevelSwitcher implements Runnable {
148 
149     public static volatile boolean shouldStop = false;
150     private final int logCount; // how many various log files will be used
151     private final String logFilePrefix; // name of log file will be logFilePrefix + index
152     private final Random RND = Utils.getRandomInstance();
153     private final MBeanServer MBS = ManagementFactory.getPlatformMBeanServer();
154 
155     /**
156      * @param logFilePrefix prefix for log files
157      * @param logCount     amount of log files
158      */
159     public LogLevelSwitcher(String logFilePrefix, int logCount) {
160         this.logCount = logCount;
161         this.logFilePrefix = logFilePrefix;
162 
163     }
164 
165     private static final String[] LOG_LEVELS = {"error", "warning", "info", "debug", "trace"};
166 
167     @Override
168     public void run() {
169 
170         while (!shouldStop) {
171             int fileNum = RND.nextInt(logCount);
172             int logLevel = RND.nextInt(LOG_LEVELS.length);
173 
174             String outputCommand = String.format("output=%s_%d.log", logFilePrefix, fileNum);
175             String logLevelCommand = "what='gc*=" + LOG_LEVELS[logLevel] + "'";
176 
177             try {
178                 Object out = MBS.invoke(new ObjectName("com.sun.management:type=DiagnosticCommand"),
179                                         "vmLog",
180                                         new Object[]{new String[]{outputCommand, logLevelCommand}},
181                                         new String[]{String[].class.getName()});
182 
183                 if (!out.toString().isEmpty()) {
184                     System.out.format("WARNING: Diagnostic command vmLog with arguments %s,%s returned not empty"
185                                     + " output %s\n",
186                             outputCommand, logLevelCommand, out);
187                 }
188             } catch (InstanceNotFoundException | MBeanException | ReflectionException | MalformedObjectNameException e) {
189                 System.out.println("Got exception trying to change log level:" + e);
190                 e.printStackTrace();
191                 throw new Error(e);
192             }
193             Thread.yield();
194         }
195         System.out.println("Log Switcher finished");
196     }
197 }
198 
199 
200 public class TestUnifiedLoggingSwitchStress {
201     /**
202      * Count of memory stressing threads
203      */
204     private static final int MEMORY_STRESSERS_COUNT = 3;
205     /**
206      * Count of log switching threads
207      */
208     private static final int LOG_LEVEL_SWITCHERS_COUNT = 2;
209     /**
210      * Count of log files created by each log switching thread
211      */
212     private static final int LOG_FILES_COUNT = 2;
213     /**
214      * Maximum amount memory allocated by each stressing thread
215      */
216     private static final int MAX_MEMORY_PER_STRESSER = (int) (Runtime.getRuntime().freeMemory()
217             / MEMORY_STRESSERS_COUNT * 0.7);
218 
219     public static void main(String[] args) throws InterruptedException {
220         if (args.length != 1) {
221             throw new Error("Test Bug: Expected duration (in seconds) wasn't provided as command line argument");
222         }
223         long duration = Integer.parseInt(args[0]) * 1000;
224 
225         long startTime = System.currentTimeMillis();
226 
227         List<Thread> threads = new LinkedList<>();
228 
229         for (int i = 0; i < LOG_LEVEL_SWITCHERS_COUNT; i++) {
230             threads.add(new Thread(new LogLevelSwitcher("Output_" + i, LOG_FILES_COUNT)));
231         }
232 
233         for (int i = 0; i < MEMORY_STRESSERS_COUNT; i++) {
234             threads.add(new Thread(new MemoryStresser(MAX_MEMORY_PER_STRESSER)));
235         }
236 
237         threads.stream().forEach(Thread::start);
238 
239         while (System.currentTimeMillis() - startTime < duration) {
240             Thread.yield();
241         }
242 
243         MemoryStresser.shouldStop = true;
244         LogLevelSwitcher.shouldStop = true;
245     }
246 }