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