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