package freenet.clients.http.wizardsteps; import freenet.clients.http.FirstTimeWizardToadlet; import freenet.config.Config; import freenet.config.ConfigException; import freenet.config.Option; import freenet.l10n.NodeL10n; import freenet.node.Node; import freenet.node.NodeClientCore; import freenet.node.NodeStarter; import freenet.support.Fields; import freenet.support.HTMLNode; import freenet.support.Logger; import freenet.support.SizeUtil; import freenet.support.api.HTTPRequest; import freenet.support.io.FileUtil; /** * Allows the user to select datastore size, considering available storage space when offering options. */ public class DATASTORE_SIZE implements Step { private final NodeClientCore core; private final Config config; public DATASTORE_SIZE(NodeClientCore core, Config config) { this.config = config; this.core = core; } @Override public void getStep(HTTPRequest request, PageHelper helper) { HTMLNode contentNode = helper.getPageContent(WizardL10n.l10n("step4Title")); HTMLNode bandwidthInfoboxContent = helper.getInfobox("infobox-header", WizardL10n.l10n("datastoreSize"), contentNode, null, false); bandwidthInfoboxContent.addChild("#", WizardL10n.l10n("datastoreSizeLong")); HTMLNode bandwidthForm = helper.addFormChild(bandwidthInfoboxContent, ".", "dsForm"); HTMLNode result = bandwidthForm.addChild("select", "name", "ds"); long maxSize = maxDatastoreSize(); long autodetectedSize = canAutoconfigureDatastoreSize(); if(maxSize < autodetectedSize) autodetectedSize = maxSize; @SuppressWarnings("unchecked") Option<Long> sizeOption = (Option<Long>) config.get("node").getOption("storeSize"); if(!sizeOption.isDefault()) { long current = sizeOption.getValue(); result.addChild("option", new String[] { "value", "selected" }, new String[] { SizeUtil.formatSize(current), "on" }, WizardL10n.l10n("currentPrefix")+" "+SizeUtil.formatSize(current)); } else if(autodetectedSize != -1) { result.addChild("option", new String[] { "value", "selected" }, new String[] { SizeUtil.formatSize(autodetectedSize), "on" }, SizeUtil.formatSize(autodetectedSize)); } if(autodetectedSize != 512*1024*1024) { result.addChild("option", "value", "512M", "512 MiB"); } // We always allow at least 1GB result.addChild("option", "value", "1G", "1 GiB"); if(maxSize >= 2l*1024*1024*1024) { if(autodetectedSize != -1 || !sizeOption.isDefault()) { result.addChild("option", "value", "2G", "2 GiB"); } else { result.addChild("option", new String[] { "value", "selected" }, new String[] { "2G", "on" }, "2GiB"); } } if(maxSize >= 3l*1024*1024*1024) result.addChild("option", "value", "3G", "3 GiB"); if(maxSize >= 5l*1024*1024*1024) result.addChild("option", "value", "5G", "5 GiB"); if(maxSize >= 10l*1024*1024*1024) result.addChild("option", "value", "10G", "10 GiB"); if(maxSize >= 20l*1024*1024*1024) result.addChild("option", "value", "20G", "20 GiB"); if(maxSize >= 50l*1024*1024*1024) result.addChild("option", "value", "50G", "50 GiB"); if(maxSize >= 200l*1024*1024*1024) result.addChild("option", "value", "200G", "200GiB"); if(maxSize >= 500l*1024*1024*1024) result.addChild("option", "value", "500G", "500GiB"); //Put buttons below dropdown. HTMLNode below = bandwidthForm.addChild("div"); below.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "back", NodeL10n.getBase().getString("Toadlet.back")}); below.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "next", NodeL10n.getBase().getString("Toadlet.next")}); } @Override public String postStep(HTTPRequest request) { // drop down options may be 6 chars or less, but formatted ones e.g. old value if re-running can be more _setDatastoreSize(request.getPartAsStringFailsafe("ds", 20)); return FirstTimeWizardToadlet.WIZARD_STEP.BANDWIDTH.name(); } private void _setDatastoreSize(String selectedStoreSize) { try { long size = Fields.parseLong(selectedStoreSize); // client cache: 10% up to 200MB long clientCacheSize = Math.min(size / 10, 200*1024*1024); // recent requests cache / slashdot cache / ULPR cache int upstreamLimit = config.get("node").getInt("outputBandwidthLimit"); int downstreamLimit = config.get("node").getInt("inputBandwidthLimit"); // is used for remote stuff, so go by the minimum of the two int limit; if(downstreamLimit <= 0) limit = upstreamLimit; else limit = Math.min(downstreamLimit, upstreamLimit); // 35KB/sec limit has been seen to have 0.5 store writes per second. // So saying we want to have space to cache everything is only doubling that ... // OTOH most stuff is at low enough HTL to go to the datastore and thus not to // the slashdot cache, so we could probably cut this significantly... long lifetime = config.get("node").getLong("slashdotCacheLifetime"); long maxSlashdotCacheSize = (lifetime / 1000) * limit; long slashdotCacheSize = Math.min(size / 10, maxSlashdotCacheSize); long storeSize = size - (clientCacheSize + slashdotCacheSize); System.out.println("Setting datastore size to "+Fields.longToString(storeSize, true)); config.get("node").set("storeSize", Fields.longToString(storeSize, true)); if(config.get("node").getString("storeType").equals("ram")) config.get("node").set("storeType", "salt-hash"); System.out.println("Setting client cache size to "+Fields.longToString(clientCacheSize, true)); config.get("node").set("clientCacheSize", Fields.longToString(clientCacheSize, true)); if(config.get("node").getString("clientCacheType").equals("ram")) config.get("node").set("clientCacheType", "salt-hash"); System.out.println("Setting slashdot/ULPR/recent requests cache size to "+Fields.longToString(slashdotCacheSize, true)); config.get("node").set("slashdotCacheSize", Fields.longToString(slashdotCacheSize, true)); Logger.normal(this, "The storeSize has been set to " + selectedStoreSize); } catch(ConfigException e) { Logger.error(this, "Should not happen, please report!" + e, e); } } private long maxDatastoreSize() { long maxMemory = NodeStarter.getMemoryLimitBytes(); if(maxMemory == Long.MAX_VALUE) return 1024*1024*1024; // Treat as don't know. if(maxMemory < 128*1024*1024) return 1024*1024*1024; // 1GB default if don't know or very small memory. // Don't use the first 100MB for slot filters. long available = maxMemory - 100*1024*1024; // Don't use more than 50% of available memory for slot filters. available = available / 2; // Slot filters are 4 bytes per slot. long slots = available / 4; // There are 3 types of keys. We want the number of { SSK, CHK, pubkey } i.e. the number of slots in each store. slots /= 3; // We return the total size, so we don't need to worry about cache vs store or even client cache. // One key of all 3 types combined uses Node.sizePerKey bytes on disk. So we get a size. return slots * Node.sizePerKey; } private long canAutoconfigureDatastoreSize() { if (!config.get("node").getOption("storeSize").isDefault()) return -1; long freeSpace = core.node.getStoreDir().getUsableSpace(); if (freeSpace <= 0) { return -1; } else { long shortSize; long oneGiB = 1024 * 1024 * 1024L; // Maximum for Freenet: 256GB. That's a 128MiB bloom filter. long bloomFilter128MiBMax = 256 * oneGiB; // Maximum to suggest to keep Disk I/O managable. This // value might need revisiting when hardware or // filesystems change. long diskIoMax = 20 * oneGiB; // Choose a suggested store size based on available free space. if (freeSpace > 50 * oneGiB) { // > 50 GiB: Use 10% free space; minimum 10 GiB. Limited by // bloom filters and disk I/O. shortSize = Math.max(10 * oneGiB, Math.min(freeSpace / 10, Math.min(diskIoMax, bloomFilter128MiBMax))); } else if (freeSpace > 5 * oneGiB) { // > 5 GiB: Use 20% free space, minimum 2 GiB. shortSize = Math.max(freeSpace / 5, 2 * oneGiB); } else if (freeSpace > 2 * oneGiB) { // > 2 GiB: 512 MiB. shortSize = 512 * (1024 * 1024); } else { // <= 2 GiB: 256 MiB. shortSize = 256 * (1024 * 1024); } return shortSize; } } }