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