001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2010-2016 ForgeRock AS. 016 */ 017package org.opends.server.backends.jeb; 018 019import static com.sleepycat.je.EnvironmentConfig.*; 020 021import static org.opends.messages.BackendMessages.*; 022import static org.opends.messages.ConfigMessages.*; 023 024import java.lang.reflect.Method; 025import java.math.BigInteger; 026import java.util.Arrays; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.SortedSet; 032import java.util.StringTokenizer; 033import java.util.concurrent.TimeUnit; 034import java.util.logging.Level; 035import java.util.logging.Logger; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.i18n.slf4j.LocalizedLogger; 039import org.forgerock.opendj.config.server.ConfigException; 040import org.forgerock.opendj.ldap.ByteString; 041import org.forgerock.opendj.ldap.DN; 042import org.opends.server.admin.BooleanPropertyDefinition; 043import org.opends.server.admin.DurationPropertyDefinition; 044import org.opends.server.admin.PropertyDefinition; 045import org.opends.server.admin.std.meta.JEBackendCfgDefn; 046import org.opends.server.admin.std.server.BackendCfg; 047import org.opends.server.admin.std.server.JEBackendCfg; 048import org.opends.server.config.ConfigConstants; 049import org.opends.server.core.DirectoryServer; 050import org.opends.server.core.MemoryQuota; 051import org.opends.server.util.Platform; 052 053import com.sleepycat.je.Durability; 054import com.sleepycat.je.EnvironmentConfig; 055import com.sleepycat.je.dbi.MemoryBudget; 056 057/** This class maps JE properties to configuration attributes. */ 058public class ConfigurableEnvironment 059{ 060 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 061 062 /** 063 * The name of the attribute which configures the database cache size as a 064 * percentage of Java VM heap size. 065 */ 066 public static final String ATTR_DATABASE_CACHE_PERCENT = 067 ConfigConstants.NAME_PREFIX_CFG + "db-cache-percent"; 068 069 /** 070 * The name of the attribute which configures the database cache size as an 071 * approximate number of bytes. 072 */ 073 public static final String ATTR_DATABASE_CACHE_SIZE = 074 ConfigConstants.NAME_PREFIX_CFG + "db-cache-size"; 075 076 /** 077 * The name of the attribute which configures whether data updated by a 078 * database transaction is forced to disk. 079 */ 080 public static final String ATTR_DATABASE_TXN_NO_SYNC = 081 ConfigConstants.NAME_PREFIX_CFG + "db-txn-no-sync"; 082 083 /** 084 * The name of the attribute which configures whether data updated by a 085 * database transaction is written from the Java VM to the O/S. 086 */ 087 public static final String ATTR_DATABASE_TXN_WRITE_NO_SYNC = 088 ConfigConstants.NAME_PREFIX_CFG + "db-txn-write-no-sync"; 089 090 /** 091 * The name of the attribute which configures whether the database background 092 * cleaner thread runs. 093 */ 094 public static final String ATTR_DATABASE_RUN_CLEANER = 095 ConfigConstants.NAME_PREFIX_CFG + "db-run-cleaner"; 096 097 /** 098 * The name of the attribute which configures the minimum percentage of log 099 * space that must be used in log files. 100 */ 101 public static final String ATTR_CLEANER_MIN_UTILIZATION = 102 ConfigConstants.NAME_PREFIX_CFG + "db-cleaner-min-utilization"; 103 104 /** 105 * The name of the attribute which configures the maximum size of each 106 * individual JE log file, in bytes. 107 */ 108 public static final String ATTR_DATABASE_LOG_FILE_MAX = 109 ConfigConstants.NAME_PREFIX_CFG + "db-log-file-max"; 110 111 /** The name of the attribute which configures the database cache eviction algorithm. */ 112 public static final String ATTR_EVICTOR_LRU_ONLY = 113 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-lru-only"; 114 115 /** 116 * The name of the attribute which configures the number of nodes in one scan 117 * of the database cache evictor. 118 */ 119 public static final String ATTR_EVICTOR_NODES_PER_SCAN = 120 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-nodes-per-scan"; 121 122 /** 123 * The name of the attribute which configures the minimum number of threads 124 * of the database cache evictor pool. 125 */ 126 public static final String ATTR_EVICTOR_CORE_THREADS = 127 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-core-threads"; 128 /** 129 * The name of the attribute which configures the maximum number of threads 130 * of the database cache evictor pool. 131 */ 132 public static final String ATTR_EVICTOR_MAX_THREADS = 133 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-max-threads"; 134 135 /** 136 * The name of the attribute which configures the time excess threads 137 * of the database cache evictor pool are kept alive. 138 */ 139 public static final String ATTR_EVICTOR_KEEP_ALIVE = 140 ConfigConstants.NAME_PREFIX_CFG + "db-evictor-keep-alive"; 141 142 /** 143 * The name of the attribute which configures whether the logging file 144 * handler will be on or off. 145 */ 146 public static final String ATTR_LOGGING_FILE_HANDLER_ON = 147 ConfigConstants.NAME_PREFIX_CFG + "db-logging-file-handler-on"; 148 149 /** The name of the attribute which configures the trace logging message level. */ 150 public static final String ATTR_LOGGING_LEVEL = 151 ConfigConstants.NAME_PREFIX_CFG + "db-logging-level"; 152 153 /** 154 * The name of the attribute which configures how many bytes are written to 155 * the log before the checkpointer runs. 156 */ 157 public static final String ATTR_CHECKPOINTER_BYTES_INTERVAL = 158 ConfigConstants.NAME_PREFIX_CFG + "db-checkpointer-bytes-interval"; 159 160 /** 161 * The name of the attribute which configures the amount of time between 162 * runs of the checkpointer. 163 */ 164 public static final String ATTR_CHECKPOINTER_WAKEUP_INTERVAL = 165 ConfigConstants.NAME_PREFIX_CFG + 166 "db-checkpointer-wakeup-interval"; 167 168 /** The name of the attribute which configures the number of lock tables. */ 169 public static final String ATTR_NUM_LOCK_TABLES = 170 ConfigConstants.NAME_PREFIX_CFG + "db-num-lock-tables"; 171 172 /** 173 * The name of the attribute which configures the number threads 174 * allocated by the cleaner for log file processing. 175 */ 176 public static final String ATTR_NUM_CLEANER_THREADS = 177 ConfigConstants.NAME_PREFIX_CFG + "db-num-cleaner-threads"; 178 179 /** The name of the attribute which configures the size of the file handle cache. */ 180 public static final String ATTR_LOG_FILECACHE_SIZE = 181 ConfigConstants.NAME_PREFIX_CFG + "db-log-filecache-size"; 182 183 /** The name of the attribute which may specify any native JE properties. */ 184 public static final String ATTR_JE_PROPERTY = 185 ConfigConstants.NAME_PREFIX_CFG + "je-property"; 186 187 /** A map of JE property names to the corresponding configuration attribute. */ 188 private static HashMap<String, String> attrMap = new HashMap<>(); 189 190 /** 191 * A map of configuration attribute names to the corresponding configuration object getter method. 192 */ 193 private static Map<String, Method> jebMethodMap = new HashMap<>(); 194 /** A map of configuration attribute names to the corresponding configuration PropertyDefinition. */ 195 private static Map<String, PropertyDefinition<?>> jebDefnMap = new HashMap<>(); 196 197 /** Pulled from resource/admin/ABBREVIATIONS.xsl. db is mose common. */ 198 private static final List<String> ABBREVIATIONS = Arrays.asList(new String[] 199 {"aci", "ip", "ssl", "dn", "rdn", "jmx", "smtp", "http", 200 "https", "ldap", "ldaps", "ldif", "jdbc", "tcp", "tls", 201 "pkcs11", "sasl", "gssapi", "md5", "je", "dse", "fifo", 202 "vlv", "uuid", "md5", "sha1", "sha256", "sha384", "sha512", 203 "tls", "db"}); 204 205 /** E.g. db-cache-percent -> DBCachePercent */ 206 private static String propNametoCamlCase(String hyphenated) 207 { 208 String[] components = hyphenated.split("\\-"); 209 StringBuilder buffer = new StringBuilder(); 210 for (String component: components) { 211 if (ABBREVIATIONS.contains(component)) { 212 buffer.append(component.toUpperCase()); 213 } else { 214 buffer.append(component.substring(0, 1).toUpperCase()).append(component.substring(1)); 215 } 216 } 217 return buffer.toString(); 218 } 219 220 /** 221 * Register a JE property and its corresponding configuration attribute. 222 * 223 * @param propertyName The name of the JE property to be registered. 224 * @param attrName The name of the configuration attribute associated 225 * with the property. 226 * @throws Exception If there is an error in the attribute name. 227 */ 228 private static void registerProp(String propertyName, String attrName) 229 throws Exception 230 { 231 // Strip off NAME_PREFIX_CFG. 232 String baseName = attrName.substring(7); 233 String methodBaseName = propNametoCamlCase(baseName); 234 235 registerJebProp(attrName, methodBaseName); 236 attrMap.put(propertyName, attrName); 237 } 238 239 private static void registerJebProp(String attrName, String methodBaseName) throws Exception 240 { 241 Class<JEBackendCfg> configClass = JEBackendCfg.class; 242 JEBackendCfgDefn defn = JEBackendCfgDefn.getInstance(); 243 Class<? extends JEBackendCfgDefn> defClass = defn.getClass(); 244 245 String propName = "get" + methodBaseName + "PropertyDefinition"; 246 PropertyDefinition<?> propDefn = (PropertyDefinition<?>) defClass.getMethod(propName).invoke(defn); 247 248 String methodPrefix = propDefn instanceof BooleanPropertyDefinition ? "is" : "get"; 249 String methodName = methodPrefix + methodBaseName; 250 251 jebDefnMap.put(attrName, propDefn); 252 jebMethodMap.put(attrName, configClass.getMethod(methodName)); 253 } 254 255 /** 256 * Get the name of the configuration attribute associated with a JE property. 257 * @param jeProperty The name of the JE property. 258 * @return The name of the associated configuration attribute. 259 */ 260 public static String getAttributeForProperty(String jeProperty) 261 { 262 return attrMap.get(jeProperty); 263 } 264 265 /** 266 * Get the value of a JE property that is mapped to a configuration attribute. 267 * @param cfg The configuration containing the property values. 268 * @param attrName The configuration attribute type name. 269 * @return The string value of the JE property. 270 */ 271 private static String getPropertyValue(BackendCfg cfg, String attrName, ByteString backendId) 272 { 273 try 274 { 275 PropertyDefinition<?> propDefn = jebDefnMap.get(attrName); 276 Method method = jebMethodMap.get(attrName); 277 278 if (propDefn instanceof DurationPropertyDefinition) 279 { 280 Long value = (Long)method.invoke(cfg); 281 282 // JE durations are in microseconds so we must convert. 283 DurationPropertyDefinition durationPropDefn = 284 (DurationPropertyDefinition)propDefn; 285 value = 1000*durationPropDefn.getBaseUnit().toMilliSeconds(value); 286 287 return String.valueOf(value); 288 } 289 else 290 { 291 Object value = method.invoke(cfg); 292 293 if (value != null) 294 { 295 return String.valueOf(value); 296 } 297 298 if (attrName.equals(ATTR_NUM_CLEANER_THREADS)) 299 { 300 // Automatically choose based on the number of processors. We will use 301 // similar heuristics to those used to define the default number of 302 // worker threads. 303 value = Platform.computeNumberOfThreads(8, 1.0f); 304 305 logger.debug(INFO_ERGONOMIC_SIZING_OF_JE_CLEANER_THREADS, 306 backendId, (Number) value); 307 } 308 else if (attrName.equals(ATTR_NUM_LOCK_TABLES)) 309 { 310 // Automatically choose based on the number of processors. We'll assume that the user has also allowed 311 // automatic configuration of cleaners and workers. 312 BigInteger tmp = BigInteger.valueOf(Platform.computeNumberOfThreads(1, 2)); 313 value = tmp.nextProbablePrime(); 314 315 logger.debug(INFO_ERGONOMIC_SIZING_OF_JE_LOCK_TABLES, backendId, (Number) value); 316 } 317 318 return String.valueOf(value); 319 } 320 } 321 catch (Exception e) 322 { 323 logger.traceException(e); 324 return ""; 325 } 326 } 327 328 static 329 { 330 // Register the parameters that have JE property names. 331 try 332 { 333 registerProp("je.maxMemoryPercent", ATTR_DATABASE_CACHE_PERCENT); 334 registerProp("je.maxMemory", ATTR_DATABASE_CACHE_SIZE); 335 registerProp("je.cleaner.minUtilization", ATTR_CLEANER_MIN_UTILIZATION); 336 registerProp("je.env.runCleaner", ATTR_DATABASE_RUN_CLEANER); 337 registerProp("je.evictor.lruOnly", ATTR_EVICTOR_LRU_ONLY); 338 registerProp("je.evictor.nodesPerScan", ATTR_EVICTOR_NODES_PER_SCAN); 339 registerProp("je.evictor.coreThreads", ATTR_EVICTOR_CORE_THREADS); 340 registerProp("je.evictor.maxThreads", ATTR_EVICTOR_MAX_THREADS); 341 registerProp("je.evictor.keepAlive", ATTR_EVICTOR_KEEP_ALIVE); 342 registerProp("je.log.fileMax", ATTR_DATABASE_LOG_FILE_MAX); 343 registerProp("je.checkpointer.bytesInterval", 344 ATTR_CHECKPOINTER_BYTES_INTERVAL); 345 registerProp("je.checkpointer.wakeupInterval", 346 ATTR_CHECKPOINTER_WAKEUP_INTERVAL); 347 registerProp("je.lock.nLockTables", ATTR_NUM_LOCK_TABLES); 348 registerProp("je.cleaner.threads", ATTR_NUM_CLEANER_THREADS); 349 registerProp("je.log.fileCacheSize", ATTR_LOG_FILECACHE_SIZE); 350 } 351 catch (Exception e) 352 { 353 logger.traceException(e); 354 } 355 } 356 357 /** 358 * Create a JE environment configuration with default values. 359 * 360 * @return A JE environment config containing default values. 361 */ 362 public static EnvironmentConfig defaultConfig() 363 { 364 EnvironmentConfig envConfig = new EnvironmentConfig(); 365 366 envConfig.setTransactional(true); 367 envConfig.setAllowCreate(true); 368 369 // "je.env.sharedLatches" is "true" by default since JE #12136 (3.3.62?) 370 371 // This parameter was set to false while diagnosing a Berkeley DB JE bug. 372 // Normally cleansed log files are deleted, but if this is set false 373 // they are instead renamed from .jdb to .del. 374 envConfig.setConfigParam(CLEANER_EXPUNGE, "true"); 375 376 // Under heavy write load the check point can fall behind causing 377 // uncontrolled DB growth over time. This parameter makes the out of 378 // the box configuration more robust at the cost of a slight 379 // reduction in maximum write throughput. Experiments have shown 380 // that response time predictability is not impacted negatively. 381 envConfig.setConfigParam(CHECKPOINTER_HIGH_PRIORITY, "true"); 382 383 // If the JVM is reasonably large then we can safely default to 384 // bigger read buffers. This will result in more scalable checkpointer 385 // and cleaner performance. 386 if (Runtime.getRuntime().maxMemory() > 256 * 1024 * 1024) 387 { 388 envConfig.setConfigParam(CLEANER_LOOK_AHEAD_CACHE_SIZE, 389 String.valueOf(2 * 1024 * 1024)); 390 envConfig.setConfigParam(LOG_ITERATOR_READ_SIZE, 391 String.valueOf(2 * 1024 * 1024)); 392 envConfig.setConfigParam(LOG_FAULT_READ_SIZE, String.valueOf(4 * 1024)); 393 } 394 395 // Disable lock timeouts, meaning that no lock wait 396 // timelimit is enforced and a deadlocked operation 397 // will block indefinitely. 398 envConfig.setLockTimeout(0, TimeUnit.MICROSECONDS); 399 400 return envConfig; 401 } 402 403 /** 404 * Parse a configuration associated with a JE environment and create an 405 * environment config from it. 406 * 407 * @param cfg The configuration to be parsed. 408 * @return An environment config instance corresponding to the config entry. 409 * @throws ConfigException If there is an error in the provided configuration 410 * entry. 411 */ 412 public static EnvironmentConfig parseConfigEntry(JEBackendCfg cfg) throws ConfigException 413 { 414 validateDbCacheSize(cfg.getDBCacheSize()); 415 416 EnvironmentConfig envConfig = defaultConfig(); 417 setDurability(envConfig, cfg.isDBTxnNoSync(), cfg.isDBTxnWriteNoSync()); 418 setJEProperties(cfg, envConfig, cfg.dn().rdn().getFirstAVA().getAttributeValue()); 419 setDBLoggingLevel(envConfig, cfg.getDBLoggingLevel(), cfg.dn(), cfg.isDBLoggingFileHandlerOn()); 420 421 // See if there are any native JE properties specified in the config 422 // and if so try to parse, evaluate and set them. 423 return setJEProperties(envConfig, cfg.getJEProperty(), attrMap); 424 } 425 426 private static void validateDbCacheSize(long dbCacheSize) throws ConfigException 427 { 428 if (dbCacheSize != 0) 429 { 430 if (MemoryBudget.getRuntimeMaxMemory() < dbCacheSize) 431 { 432 throw new ConfigException(ERR_BACKEND_CONFIG_CACHE_SIZE_GREATER_THAN_JVM_HEAP.get( 433 dbCacheSize, MemoryBudget.getRuntimeMaxMemory())); 434 } 435 if (dbCacheSize < MemoryBudget.MIN_MAX_MEMORY_SIZE) 436 { 437 throw new ConfigException(ERR_CONFIG_JEB_CACHE_SIZE_TOO_SMALL.get( 438 dbCacheSize, MemoryBudget.MIN_MAX_MEMORY_SIZE)); 439 } 440 MemoryQuota memoryQuota = DirectoryServer.getInstance().getServerContext().getMemoryQuota(); 441 if (!memoryQuota.acquireMemory(dbCacheSize)) 442 { 443 logger.warn(ERR_BACKEND_CONFIG_CACHE_SIZE_GREATER_THAN_JVM_HEAP.get( 444 dbCacheSize, memoryQuota.getMaxMemory())); 445 } 446 } 447 } 448 449 private static void setDurability(EnvironmentConfig envConfig, boolean dbTxnNoSync, boolean dbTxnWriteNoSync) 450 throws ConfigException 451 { 452 if (dbTxnNoSync && dbTxnWriteNoSync) 453 { 454 throw new ConfigException(ERR_CONFIG_JEB_DURABILITY_CONFLICT.get()); 455 } 456 if (dbTxnNoSync) 457 { 458 envConfig.setDurability(Durability.COMMIT_NO_SYNC); 459 } 460 else if (dbTxnWriteNoSync) 461 { 462 envConfig.setDurability(Durability.COMMIT_WRITE_NO_SYNC); 463 } 464 } 465 466 private static void setJEProperties(BackendCfg cfg, EnvironmentConfig envConfig, ByteString backendId) 467 { 468 for (Map.Entry<String, String> mapEntry : attrMap.entrySet()) 469 { 470 String jeProperty = mapEntry.getKey(); 471 String attrName = mapEntry.getValue(); 472 473 String value = getPropertyValue(cfg, attrName, backendId); 474 envConfig.setConfigParam(jeProperty, value); 475 } 476 } 477 478 private static void setDBLoggingLevel(EnvironmentConfig envConfig, String loggingLevel, DN dn, 479 boolean loggingFileHandlerOn) throws ConfigException 480 { 481 Logger parent = Logger.getLogger("com.sleepycat.je"); 482 try 483 { 484 parent.setLevel(Level.parse(loggingLevel)); 485 } 486 catch (Exception e) 487 { 488 throw new ConfigException(ERR_JEB_INVALID_LOGGING_LEVEL.get(loggingLevel, dn)); 489 } 490 491 final Level level = loggingFileHandlerOn ? Level.ALL : Level.OFF; 492 envConfig.setConfigParam(FILE_LOGGING_LEVEL, level.getName()); 493 } 494 495 /** 496 * Parse, validate and set native JE environment properties for 497 * a given environment config. 498 * 499 * @param envConfig The JE environment config for which to set 500 * the properties. 501 * @param jeProperties The JE environment properties to parse, 502 * validate and set. 503 * @param configAttrMap Component supported JE properties to 504 * their configuration attributes map. 505 * @return An environment config instance with given properties 506 * set. 507 * @throws ConfigException If there is an error while parsing, 508 * validating and setting any of the properties provided. 509 */ 510 public static EnvironmentConfig setJEProperties(EnvironmentConfig envConfig, 511 SortedSet<String> jeProperties, HashMap<String, String> configAttrMap) 512 throws ConfigException 513 { 514 if (jeProperties.isEmpty()) { 515 // return default config. 516 return envConfig; 517 } 518 519 // Set to catch duplicate properties. 520 HashSet<String> uniqueJEProperties = new HashSet<>(); 521 522 // Iterate through the config values associated with a JE property. 523 for (String jeEntry : jeProperties) 524 { 525 StringTokenizer st = new StringTokenizer(jeEntry, "="); 526 if (st.countTokens() != 2) 527 { 528 throw new ConfigException(ERR_CONFIG_JE_PROPERTY_INVALID_FORM.get(jeEntry)); 529 } 530 531 String jePropertyName = st.nextToken(); 532 String jePropertyValue = st.nextToken(); 533 // Check if it is a duplicate. 534 if (uniqueJEProperties.contains(jePropertyName)) { 535 throw new ConfigException(ERR_CONFIG_JE_DUPLICATE_PROPERTY.get(jePropertyName)); 536 } 537 538 // Set JE property. 539 try { 540 envConfig.setConfigParam(jePropertyName, jePropertyValue); 541 // If this property shadows an existing config attribute. 542 if (configAttrMap.containsKey(jePropertyName)) { 543 LocalizableMessage message = ERR_CONFIG_JE_PROPERTY_SHADOWS_CONFIG.get( 544 jePropertyName, attrMap.get(jePropertyName)); 545 throw new ConfigException(message); 546 } 547 // Add this property to unique set. 548 uniqueJEProperties.add(jePropertyName); 549 } catch(IllegalArgumentException e) { 550 logger.traceException(e); 551 LocalizableMessage message = ERR_CONFIG_JE_PROPERTY_INVALID.get(jeEntry, e.getMessage()); 552 throw new ConfigException(message, e.getCause()); 553 } 554 } 555 556 return envConfig; 557 } 558}