1 /* 2 * Copyright (C) 2020, 2021 Vladimir Panteleev <btdu@cy.md> 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public 6 * License v2 as published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 * General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public 14 * License along with this program; if not, write to the 15 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 16 * Boston, MA 021110-1307, USA. 17 */ 18 19 /// btdu entry point 20 module btdu.main; 21 22 import core.time; 23 24 import std.conv : to; 25 import std.exception; 26 import std.parallelism : totalCPUs; 27 import std.path; 28 import std.random; 29 import std.socket; 30 import std.stdio; 31 import std..string; 32 33 import ae.utils.funopt; 34 import ae.utils.main; 35 import ae.utils.time.parsedur; 36 37 import btdu.browser; 38 import btdu.common; 39 import btdu.paths; 40 import btdu.sample; 41 import btdu.subproc; 42 import btdu.state; 43 44 @(`Sampling disk usage profiler for btrfs.`) 45 void program( 46 Parameter!(string, "Path to the root of the filesystem to analyze") path, 47 Option!(uint, "Number of sampling subprocesses\n (default is number of logical CPUs for this system)", "N", 'j') procs = 0, 48 Option!(Seed, "Random seed used to choose samples") seed = 0, 49 Switch!hiddenOption subprocess = false, 50 Option!(string, hiddenOption) benchmark = null, 51 Switch!("Expert mode: collect and show additional metrics.\nUses more memory.") expert = false, 52 Switch!hiddenOption man = false, 53 ) 54 { 55 if (man) 56 { 57 stdout.write(generateManPage!program( 58 "btdu", 59 ".B btdu 60 is a sampling disk usage profiler for btrfs. 61 62 For a detailed description, please see the full documentation: 63 64 .I https://github.com/CyberShadow/btdu#readme", 65 null, 66 `.SH BUGS 67 Please report defects and enhancement requests to the GitHub issue tracker: 68 69 .I https://github.com/CyberShadow/btdu/issues 70 71 .SH AUTHORS 72 73 \fBbtdu\fR is written by Vladimir Panteleev <btdu@c\fRy.m\fRd> and contributors: 74 75 .I https://github.com/CyberShadow/btdu/graphs/contributors 76 `, 77 )); 78 return; 79 } 80 81 rndGen = Random(seed); 82 fsPath = path.buildNormalizedPath; 83 84 if (subprocess) 85 return subprocessMain(path); 86 87 checkBtrfs(fsPath); 88 89 if (procs == 0) 90 procs = totalCPUs; 91 92 bool headless; 93 Duration benchmarkTime; 94 ulong benchmarkSamples; 95 if (benchmark) 96 { 97 headless = true; 98 if (isNumeric(benchmark[])) 99 benchmarkSamples = benchmark.to!ulong; 100 else 101 benchmarkTime = parseDuration(benchmark); 102 } 103 104 subprocesses = new Subprocess[procs]; 105 foreach (ref subproc; subprocesses) 106 subproc.start(); 107 108 Socket stdinSocket; 109 if (!headless) 110 { 111 stdinSocket = new Socket(cast(socket_t)stdin.fileno, AddressFamily.UNSPEC); 112 stdinSocket.blocking = false; 113 } 114 115 .expert = expert; 116 117 Browser browser; 118 if (!headless) 119 { 120 browser.start(); 121 browser.update(); 122 } 123 124 auto startTime = MonoTime.currTime(); 125 enum refreshInterval = 500.msecs; 126 auto nextRefresh = startTime; 127 128 auto readSet = new SocketSet; 129 auto exceptSet = new SocketSet; 130 131 // Main event loop 132 while (true) 133 { 134 readSet.reset(); 135 exceptSet.reset(); 136 if (stdinSocket) 137 { 138 readSet.add(stdinSocket); 139 exceptSet.add(stdinSocket); 140 } 141 if (!paused) 142 foreach (ref subproc; subprocesses) 143 readSet.add(subproc.socket); 144 145 Socket.select(readSet, null, exceptSet); 146 auto now = MonoTime.currTime(); 147 148 if (stdinSocket && browser.handleInput()) 149 { 150 do {} while (browser.handleInput()); // Process all input 151 if (browser.done) 152 break; 153 browser.update(); 154 nextRefresh = now + refreshInterval; 155 } 156 if (!paused) 157 foreach (ref subproc; subprocesses) 158 if (readSet.isSet(subproc.socket)) 159 subproc.handleInput(); 160 if (!headless && now > nextRefresh) 161 { 162 browser.update(); 163 nextRefresh = now + refreshInterval; 164 } 165 if (benchmarkTime && now > startTime + benchmarkTime) 166 break; 167 if (benchmarkSamples && browserRoot.data[SampleType.represented].samples >= benchmarkSamples) 168 break; 169 } 170 171 if (benchmarkTime) 172 writeln(browserRoot.data[SampleType.represented].samples); 173 if (benchmarkSamples) 174 writeln(MonoTime.currTime() - startTime); 175 } 176 177 void checkBtrfs(string fsPath) 178 { 179 import core.sys.posix.fcntl : open, O_RDONLY; 180 import std..string : toStringz; 181 import btrfs : isBTRFS, isSubvolume, getSubvolumeID; 182 import btrfs.c.kernel_shared.ctree : BTRFS_FS_TREE_OBJECTID; 183 184 int fd = open(fsPath.toStringz, O_RDONLY); 185 errnoEnforce(fd >= 0, "open"); 186 187 enforce(fd.isBTRFS, 188 fsPath ~ " is not a btrfs filesystem"); 189 190 enforce(fd.isSubvolume, 191 fsPath ~ " is not the root of a btrfs subvolume - please specify the path to the subvolume root"); 192 193 enforce(fd.getSubvolumeID() == BTRFS_FS_TREE_OBJECTID, 194 fsPath ~ " is not the root btrfs subvolume - please specify the path to a mountpoint mounted with subvol=/ or subvolid=5"); 195 } 196 197 void usageFun(string usage) 198 { 199 stderr.writeln("btdu v" ~ btduVersion); 200 stderr.writeln(usage); 201 } 202 203 mixin main!(funopt!(program, FunOptConfig.init, usageFun));