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