JAL-4373 Setting the jvmmempc and jvmmemmax strings from the Arg.getName() for consis...
[jalview.git] / src / jalview / bin / MemorySetting.java
1 /*
2
3   private static String ADJUSTMENT_MESSAGE = null;
4  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
5  * Copyright (C) $$Year-Rel$$ The Jalview Authors
6  * 
7  * This file is part of Jalview.
8  * 
9  * Jalview is free software: you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License 
11  * as published by the Free Software Foundation, either version 3
12  * of the License, or (at your option) any later version.
13  *  
14  * Jalview is distributed in the hope that it will be useful, but 
15  * WITHOUT ANY WARRANTY; without even the implied warranty 
16  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
17  * PURPOSE.  See the GNU General Public License for more details.
18  * 
19  * You should have received a copy of the GNU General Public License
20  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
21  * The Jalview Authors are detailed in the 'AUTHORS' file.
22  */
23 package jalview.bin;
24
25 import java.util.Locale;
26
27 import jalview.bin.argparser.Arg;
28
29 /**
30  * Methods to decide on appropriate memory setting for Jalview based on two
31  * optionally provided values: jvmmempc - the maximum percentage of total
32  * physical memory to allocate, and jvmmemmax - the maximum absolute amount of
33  * physical memory to allocate. These can be provided as arguments or system
34  * properties. Other considerations such as minimum application requirements and
35  * leaving space for OS are used too.
36  * 
37  * @author bsoares
38  *
39  */
40 public class MemorySetting
41 {
42   public static final String MAX_HEAPSIZE_PERCENT_PROPERTY_NAME = Arg.JVMMEMPC
43           .getName();
44
45   public static final String MAX_HEAPSIZE_PROPERTY_NAME = Arg.JVMMEMMAX
46           .getName();
47
48   private static final int MAX_HEAPSIZE_PERCENT_DEFAULT = 90; // 90%
49
50   private static final long GIGABYTE = 1073741824; // 1GB
51
52   public static final long LEAVE_FREE_MIN_MEMORY = GIGABYTE / 2;
53
54   public static final long APPLICATION_MIN_MEMORY = GIGABYTE / 2;
55
56   private static final long MAX_HEAPSIZE_GB_DEFAULT = 32;
57
58   private static final long NOMEM_MAX_HEAPSIZE_GB_DEFAULT = 8;
59
60   public static final String NS = "MEMORY";
61
62   public static final String CUSTOMISED_SETTINGS = NS
63           + "_CUSTOMISED_SETTINGS";
64
65   public static final String MEMORY_JVMMEMPC = NS + "_"
66           + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.toUpperCase(Locale.ROOT);
67
68   public static final String MEMORY_JVMMEMMAX = NS + "_"
69           + MAX_HEAPSIZE_PROPERTY_NAME.toUpperCase(Locale.ROOT);
70
71   protected static boolean logToClassChecked = false;
72
73   public static String memorySuffixes = "bkmgt"; // order of the suffixes is
74                                                  // important!
75
76   public static long getMemorySetting()
77   {
78     return getMemorySetting(null, null);
79   }
80
81   public static long getMemorySetting(String jvmmemmaxarg,
82           String jvmmempcarg)
83   {
84     return getMemorySetting(jvmmemmaxarg, jvmmempcarg, true, false);
85   }
86
87   /**
88    * Decide on appropriate memory setting for Jalview based on the two arguments
89    * values: jvmmempc - the maximum percentage of total physical memory to
90    * allocate, and jvmmemmax - the maximum absolute amount of physical memory to
91    * allocate. These can be provided as arguments. If not provided as arguments
92    * (or set as null) system properties will be used instead (if set). The
93    * memory setting returned will be the lower of the two values. If either of
94    * the values are not provided then defaults will be used (jvmmempc=90,
95    * jvmmemmax=32GB). If total physical memory can't be ascertained when
96    * jvmmempc was set or neither jvmmempc nor jvmmemmax were set, then jvmmemmax
97    * defaults to a much safer 8GB. In this case explicitly setting jvmmemmax and
98    * not setting jvmmempc can set a higher memory for Jalview. The calculation
99    * also tries to ensure 0.5GB memory for the OS, but also tries to ensure at
100    * least 0.5GB memory for Jalview (which takes priority over the OS) If there
101    * is less then 0.5GB of physical memory then the total physical memory is
102    * used for Jalview.
103    * 
104    * @param jvmmemmaxarg
105    *          Maximum value of memory to set. This can be a numeric string
106    *          optionally followed by "b", "k", "m", "g", "t" (case insensitive)
107    *          to indicate bytes, kilobytes, megabytes, gigabytes, terabytes
108    *          respectively. If null a default value of 32G will be used. If null
109    *          and either physical memory can't be determined then the default is
110    *          8GB.
111    * @param jvmmempcarg
112    *          Max percentage of physical memory to use. Defaults to "90".
113    * 
114    * @param useProps
115    *          boolean to decide whether to look at System properties.
116    * 
117    * @return The amount of memory (in bytes) to allocate to Jalview
118    */
119   public static long getMemorySetting(String jvmmemmaxarg,
120           String jvmmempcarg, boolean useProps, boolean quiet)
121   {
122     // actual Xmx value-to-be
123     long maxMemLong = -1;
124     clearAdjustmentMessage();
125
126     // (absolute) jvmmaxmem setting, start with default
127     long memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE;
128     if (jvmmemmaxarg == null && useProps)
129     {
130       jvmmemmaxarg = System.getProperty(MAX_HEAPSIZE_PROPERTY_NAME);
131     }
132     String jvmmemmax = jvmmemmaxarg;
133     if (jvmmemmax != null && jvmmemmax.length() > 0)
134     {
135       // parse the arg
136       try
137       {
138         memmax = memoryStringToLong(jvmmemmax);
139         if (memmax == 0)
140         {
141           throw (new NumberFormatException("Not allowing 0"));
142         }
143       } catch (NumberFormatException e)
144       {
145         memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE;
146         setAdjustmentMessage("MemorySetting Property '"
147                 + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg
148                 + "') badly formatted or 0, using default ("
149                 + MAX_HEAPSIZE_GB_DEFAULT + "g).", quiet);
150       }
151
152       // check at least minimum value (this accounts for negatives too)
153       if (memmax < APPLICATION_MIN_MEMORY)
154       {
155         memmax = APPLICATION_MIN_MEMORY;
156         setAdjustmentMessage("MemorySetting Property '"
157                 + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg
158                 + ") too small, using minimum (" + APPLICATION_MIN_MEMORY
159                 + ").", quiet);
160       }
161
162     }
163     else
164     {
165       // no need to warn if no setting
166       // adjustmentMessage("MemorySetting Property '" + maxHeapSizeProperty
167       // + "' not
168       // set.");
169     }
170
171     // get max percent of physical memory, starting with default
172     float percent = MAX_HEAPSIZE_PERCENT_DEFAULT;
173     if (jvmmempcarg == null && useProps)
174     {
175       jvmmempcarg = System.getProperty(MAX_HEAPSIZE_PERCENT_PROPERTY_NAME);
176     }
177     String jvmmempc = jvmmempcarg;
178     long mempc = -1;
179     try
180     {
181       if (jvmmempc != null)
182       {
183         int trypercent = Integer.parseInt(jvmmempc);
184         if (0 <= trypercent && trypercent <= 100)
185         {
186           percent = trypercent;
187         }
188         else
189         {
190           setAdjustmentMessage("MemorySetting Property '"
191                   + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME
192                   + "' should be in range 0..100. Using default " + percent
193                   + "%", quiet);
194         }
195       }
196     } catch (NumberFormatException e)
197     {
198       setAdjustmentMessage("MemorySetting property '"
199               + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg
200               + ") badly formatted", quiet);
201     }
202
203     // catch everything in case of no com.sun.management.OperatingSystemMXBean
204     boolean memoryPercentError = false;
205     try
206     {
207       long physicalMem = GetMemory.getPhysicalMemory();
208       if (physicalMem > APPLICATION_MIN_MEMORY)
209       {
210         // try and set at least applicationMinMemory and thereafter ensure
211         // leaveFreeMinMemory is left for the OS
212
213         mempc = (long) ((physicalMem / 100F) * percent);
214
215         // check for memory left for OS
216         boolean reducedmempc = false;
217         if (physicalMem - mempc < LEAVE_FREE_MIN_MEMORY)
218         {
219           mempc = physicalMem - LEAVE_FREE_MIN_MEMORY;
220           reducedmempc = true;
221           setAdjustmentMessage("MemorySetting Property '"
222                   + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg
223                   + ") too large. Leaving free space for OS and reducing to ("
224                   + mempc + ").", quiet);
225         }
226
227         // check for minimum application memsize
228         if (mempc < APPLICATION_MIN_MEMORY)
229         {
230           if (reducedmempc)
231           {
232             setAdjustmentMessage("Reduced MemorySetting (" + mempc
233                     + ") too small. Increasing to application minimum ("
234                     + APPLICATION_MIN_MEMORY + ").", quiet);
235           }
236           else
237           {
238             setAdjustmentMessage("MemorySetting Property '"
239                     + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' ("
240                     + jvmmempcarg + ") too small. Using minimum ("
241                     + APPLICATION_MIN_MEMORY + ").", quiet);
242           }
243           mempc = APPLICATION_MIN_MEMORY;
244         }
245       }
246       else
247       {
248         // not enough memory for application, just try and grab what we can!
249         mempc = physicalMem;
250         setAdjustmentMessage(
251                 "Not enough physical memory for application. Ignoring MemorySetting Property '"
252                         + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' ("
253                         + jvmmempcarg
254                         + "). Using maximum memory available ("
255                         + physicalMem + ").",
256                 quiet);
257       }
258
259     } catch (Throwable t)
260     {
261       memoryPercentError = true;
262       setAdjustmentMessage(
263               "Problem calling GetMemory.getPhysicalMemory(). Likely to be problem with com.sun.management.OperatingSystemMXBean",
264               quiet);
265       t.printStackTrace();
266     }
267
268     // In the case of an error reading the percentage of physical memory (when
269     // jvmmempc was set OR neither jvmmempc nor jvmmemmax were set), let's cap
270     // maxMemLong to 8GB
271     if (memoryPercentError && mempc == -1
272             && !(jvmmempcarg == null && jvmmemmaxarg != null) // the same as
273                                                               // (jvmmempcarg !=
274                                                               // null ||
275                                                               // (jvmmempcarg ==
276                                                               // null &&
277                                                               // jvmmemmaxarg
278                                                               // == null))
279             && memmax > NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE)
280     {
281       setAdjustmentMessage(
282               "Capping maximum memory to " + NOMEM_MAX_HEAPSIZE_GB_DEFAULT
283                       + "g due to failure to read physical memory size.",
284               quiet);
285       memmax = NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE;
286     }
287
288     if (mempc == -1) // percentage memory not set
289     {
290       maxMemLong = memmax;
291     }
292     else
293     {
294       maxMemLong = Math.min(mempc, memmax);
295     }
296
297     return maxMemLong;
298   }
299
300   public static boolean isValidMemoryString(String text)
301   {
302     if (text.length() > 0)
303     {
304       char lastChar = text.charAt(text.length() - 1);
305       char[] otherChars = text.substring(0, text.length() - 1)
306               .toCharArray();
307       for (char c : otherChars)
308       {
309         if (c < '0' || c > '9')
310         {
311           return false;
312         }
313       }
314       if ((lastChar < '0' || lastChar > '9') && memorySuffixes
315               .indexOf(Character.toLowerCase(lastChar)) == -1)
316       {
317         return false;
318       }
319     }
320     return true;
321   }
322
323   public static long memoryStringToLong(String memString)
324           throws NumberFormatException
325   {
326     if (!isValidMemoryString(memString)) // not valid
327     {
328       throw (new NumberFormatException("Not a valid memory string"));
329     }
330     char suffix = Character
331             .toLowerCase(memString.charAt(memString.length() - 1));
332     if ('0' <= suffix && suffix <= '9') // no suffix
333     {
334       return Long.valueOf(memString);
335     }
336     if (memorySuffixes.indexOf(suffix) == -1) // suffix is unknown
337     {
338       return -1;
339     }
340
341     long multiplier = (long) Math.pow(2,
342             memorySuffixes.indexOf(suffix) * 10); // note order of suffixes in
343                                                   // memorySuffixes important
344                                                   // here!
345     // parse the arg. NumberFormatExceptions passed on to calling method
346     long mem = Long
347             .parseLong(memString.substring(0, memString.length() - 1));
348     if (mem == 0)
349     {
350       return 0;
351     }
352
353     // apply multiplier only if result is not too big (i.e. bigger than a long)
354     if (Long.MAX_VALUE / mem > multiplier)
355     {
356       return multiplier * mem;
357     }
358     else
359     {
360       // number too big for a Long. Limit to Long.MAX_VALUE
361       jalview.bin.Console.outPrintln("Memory parsing of '" + memString
362               + "' produces number too big.  Limiting to Long.MAX_VALUE="
363               + Long.MAX_VALUE);
364       return Long.MAX_VALUE;
365     }
366   }
367
368   public static String memoryLongToString(long mem)
369   {
370     return memoryLongToString(mem, "%.3f");
371   }
372
373   public static String memoryLongToString(long mem, String format)
374   {
375     int exponent = 0;
376     float num = mem;
377     char suffix = 'b';
378
379     for (int i = 0; i < memorySuffixes.length(); i++)
380     {
381       char s = Character.toUpperCase(memorySuffixes.charAt(i));
382       if (mem < (long) Math.pow(2, exponent + 10)
383               || i == memorySuffixes.length() - 1) // last suffix
384       {
385         suffix = s;
386         num = (float) (mem / Math.pow(2, exponent));
387         break;
388       }
389       exponent += 10;
390     }
391
392     return String.format(format, num) + suffix;
393   }
394
395   private static String ADJUSTMENT_MESSAGE = null;
396
397   private static void setAdjustmentMessage(String reason, boolean quiet)
398   {
399     ADJUSTMENT_MESSAGE = reason;
400     if (!quiet)
401     {
402       jalview.bin.Console.outPrintln(reason);
403     }
404   }
405
406   public static void clearAdjustmentMessage()
407   {
408     ADJUSTMENT_MESSAGE = null;
409   }
410
411   public static String getAdjustmentMessage()
412   {
413     return ADJUSTMENT_MESSAGE;
414   }
415
416 }