]>
Commit | Line | Data |
---|---|---|
2745fea5 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
2745fea5 AR |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
9 | /* DEBUG: section 47 Store Directory Routines */ | |
10 | ||
11 | #include "squid.h" | |
a4dd5bfa | 12 | #include "base/IoManip.h" |
5d84beb5 EB |
13 | #include "cache_cf.h" |
14 | #include "ConfigParser.h" | |
675b8408 AR |
15 | #include "debug/Messages.h" |
16 | #include "debug/Stream.h" | |
2745fea5 | 17 | #include "globals.h" |
5d84beb5 | 18 | #include "sbuf/Stream.h" |
2745fea5 AR |
19 | #include "SquidConfig.h" |
20 | #include "Store.h" | |
21 | #include "store/Disk.h" | |
22 | #include "store/Disks.h" | |
8ecbe78d | 23 | #include "store_rebuild.h" |
70ac5b29 | 24 | #include "StoreFileSystem.h" |
2745fea5 | 25 | #include "swap_log_op.h" |
5d84beb5 | 26 | #include "tools.h" |
2745fea5 | 27 | |
5d84beb5 EB |
28 | typedef SwapDir *STDIRSELECT(const StoreEntry *e); |
29 | ||
2745fea5 AR |
30 | static STDIRSELECT storeDirSelectSwapDirRoundRobin; |
31 | static STDIRSELECT storeDirSelectSwapDirLeastLoad; | |
024aeeee | 32 | /** |
2745fea5 AR |
33 | * This function pointer is set according to 'store_dir_select_algorithm' |
34 | * in squid.conf. | |
35 | */ | |
5d84beb5 | 36 | static STDIRSELECT *storeDirSelectSwapDir = storeDirSelectSwapDirLeastLoad; |
2745fea5 | 37 | |
5ca027f0 AR |
38 | /// The entry size to use for Disk::canStore() size limit checks. |
39 | /// This is an optimization to avoid similar calculations in every cache_dir. | |
40 | static int64_t | |
41 | objectSizeForDirSelection(const StoreEntry &entry) | |
42 | { | |
43 | // entry.objectLen() is negative here when we are still STORE_PENDING | |
44 | int64_t minSize = entry.mem_obj->expectedReplySize(); | |
45 | ||
46 | // If entry size is unknown, use already accumulated bytes as an estimate. | |
47 | // Controller::accumulateMore() guarantees that there are enough of them. | |
48 | if (minSize < 0) | |
49 | minSize = entry.mem_obj->endOffset(); | |
50 | ||
51 | assert(minSize >= 0); | |
52 | minSize += entry.mem_obj->swap_hdr_sz; | |
53 | return minSize; | |
54 | } | |
55 | ||
5d84beb5 EB |
56 | /// TODO: Remove when cache_dir-iterating functions are converted to Disks methods |
57 | static SwapDir & | |
58 | SwapDirByIndex(const int i) | |
59 | { | |
60 | assert(i >= 0); | |
61 | assert(i < Config.cacheSwap.n_allocated); | |
62 | const auto sd = INDEXSD(i); | |
63 | assert(sd); | |
64 | return *sd; | |
65 | } | |
66 | ||
024aeeee | 67 | /** |
2745fea5 AR |
68 | * This new selection scheme simply does round-robin on all SwapDirs. |
69 | * A SwapDir is skipped if it is over the max_size (100%) limit, or | |
70 | * overloaded. | |
71 | */ | |
5d84beb5 | 72 | static SwapDir * |
2745fea5 AR |
73 | storeDirSelectSwapDirRoundRobin(const StoreEntry * e) |
74 | { | |
5ca027f0 | 75 | const int64_t objsize = objectSizeForDirSelection(*e); |
2745fea5 AR |
76 | |
77 | // Increment the first candidate once per selection (not once per | |
78 | // iteration) to reduce bias when some disk(s) attract more entries. | |
79 | static int firstCandidate = 0; | |
80 | if (++firstCandidate >= Config.cacheSwap.n_configured) | |
81 | firstCandidate = 0; | |
82 | ||
83 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
84 | const int dirn = (firstCandidate + i) % Config.cacheSwap.n_configured; | |
5d84beb5 | 85 | auto &dir = SwapDirByIndex(dirn); |
2745fea5 AR |
86 | |
87 | int load = 0; | |
5d84beb5 | 88 | if (!dir.canStore(*e, objsize, load)) |
2745fea5 AR |
89 | continue; |
90 | ||
91 | if (load < 0 || load > 1000) { | |
92 | continue; | |
93 | } | |
94 | ||
5d84beb5 | 95 | return &dir; |
2745fea5 AR |
96 | } |
97 | ||
5d84beb5 | 98 | return nullptr; |
2745fea5 AR |
99 | } |
100 | ||
024aeeee | 101 | /** |
2745fea5 AR |
102 | * Spread load across all of the store directories |
103 | * | |
104 | * Note: We should modify this later on to prefer sticking objects | |
105 | * in the *tightest fit* swapdir to conserve space, along with the | |
106 | * actual swapdir usage. But for now, this hack will do while | |
107 | * testing, so you should order your swapdirs in the config file | |
108 | * from smallest max-size= to largest max-size=. | |
109 | * | |
110 | * We also have to choose nleast == nconf since we need to consider | |
111 | * ALL swapdirs, regardless of state. Again, this is a hack while | |
112 | * we sort out the real usefulness of this algorithm. | |
113 | */ | |
5d84beb5 | 114 | static SwapDir * |
2745fea5 AR |
115 | storeDirSelectSwapDirLeastLoad(const StoreEntry * e) |
116 | { | |
117 | int64_t most_free = 0; | |
5ca027f0 | 118 | int64_t best_objsize = -1; |
2745fea5 AR |
119 | int least_load = INT_MAX; |
120 | int load; | |
5d84beb5 | 121 | SwapDir *selectedDir = nullptr; |
2745fea5 | 122 | int i; |
2745fea5 | 123 | |
5ca027f0 | 124 | const int64_t objsize = objectSizeForDirSelection(*e); |
2745fea5 AR |
125 | |
126 | for (i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
5d84beb5 EB |
127 | auto &sd = SwapDirByIndex(i); |
128 | sd.flags.selected = false; | |
2745fea5 | 129 | |
5d84beb5 | 130 | if (!sd.canStore(*e, objsize, load)) |
2745fea5 AR |
131 | continue; |
132 | ||
133 | if (load < 0 || load > 1000) | |
134 | continue; | |
135 | ||
136 | if (load > least_load) | |
137 | continue; | |
138 | ||
5d84beb5 | 139 | const int64_t cur_free = sd.maxSize() - sd.currentSize(); |
2745fea5 AR |
140 | |
141 | /* If the load is equal, then look in more details */ | |
142 | if (load == least_load) { | |
5ca027f0 AR |
143 | /* best max-size fit */ |
144 | if (best_objsize != -1) { | |
145 | // cache_dir with the smallest max-size gets the known-size object | |
146 | // cache_dir with the largest max-size gets the unknown-size object | |
5d84beb5 EB |
147 | if ((objsize != -1 && sd.maxObjectSize() > best_objsize) || |
148 | (objsize == -1 && sd.maxObjectSize() < best_objsize)) | |
2745fea5 | 149 | continue; |
5ca027f0 | 150 | } |
2745fea5 AR |
151 | |
152 | /* most free */ | |
153 | if (cur_free < most_free) | |
154 | continue; | |
155 | } | |
156 | ||
157 | least_load = load; | |
5d84beb5 | 158 | best_objsize = sd.maxObjectSize(); |
2745fea5 | 159 | most_free = cur_free; |
5d84beb5 | 160 | selectedDir = &sd; |
2745fea5 AR |
161 | } |
162 | ||
5d84beb5 EB |
163 | if (selectedDir) |
164 | selectedDir->flags.selected = true; | |
2745fea5 | 165 | |
5d84beb5 | 166 | return selectedDir; |
2745fea5 AR |
167 | } |
168 | ||
5ca027f0 AR |
169 | Store::Disks::Disks(): |
170 | largestMinimumObjectSize(-1), | |
171 | largestMaximumObjectSize(-1), | |
172 | secondLargestMaximumObjectSize(-1) | |
173 | { | |
174 | } | |
175 | ||
2745fea5 AR |
176 | SwapDir * |
177 | Store::Disks::store(int const x) const | |
178 | { | |
5d84beb5 | 179 | return &SwapDirByIndex(x); |
2745fea5 AR |
180 | } |
181 | ||
182 | SwapDir & | |
daed75a9 | 183 | Store::Disks::Dir(const int i) |
2745fea5 | 184 | { |
5d84beb5 | 185 | return SwapDirByIndex(i); |
2745fea5 AR |
186 | } |
187 | ||
188 | int | |
189 | Store::Disks::callback() | |
190 | { | |
191 | int result = 0; | |
192 | int j; | |
193 | static int ndir = 0; | |
194 | ||
195 | do { | |
196 | j = 0; | |
197 | ||
198 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
199 | if (ndir >= Config.cacheSwap.n_configured) | |
200 | ndir = ndir % Config.cacheSwap.n_configured; | |
201 | ||
202 | int temp_result = store(ndir)->callback(); | |
203 | ||
204 | ++ndir; | |
205 | ||
206 | j += temp_result; | |
207 | ||
208 | result += temp_result; | |
209 | ||
210 | if (j > 100) | |
211 | fatal ("too much io\n"); | |
212 | } | |
213 | } while (j > 0); | |
214 | ||
215 | ++ndir; | |
216 | ||
217 | return result; | |
218 | } | |
219 | ||
220 | void | |
221 | Store::Disks::create() | |
222 | { | |
223 | if (Config.cacheSwap.n_configured == 0) { | |
224 | debugs(0, DBG_PARSE_NOTE(DBG_CRITICAL), "No cache_dir stores are configured."); | |
225 | } | |
226 | ||
227 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
daed75a9 | 228 | if (Dir(i).active()) |
2745fea5 AR |
229 | store(i)->create(); |
230 | } | |
231 | } | |
232 | ||
233 | StoreEntry * | |
234 | Store::Disks::get(const cache_key *key) | |
235 | { | |
236 | if (const int cacheDirs = Config.cacheSwap.n_configured) { | |
237 | // ask each cache_dir until the entry is found; use static starting | |
238 | // point to avoid asking the same subset of disks more often | |
239 | // TODO: coordinate with put() to be able to guess the right disk often | |
240 | static int idx = 0; | |
241 | for (int n = 0; n < cacheDirs; ++n) { | |
242 | idx = (idx + 1) % cacheDirs; | |
5d84beb5 EB |
243 | auto &sd = Dir(idx); |
244 | if (!sd.active()) | |
2745fea5 AR |
245 | continue; |
246 | ||
5d84beb5 | 247 | if (auto e = sd.get(key)) { |
2745fea5 AR |
248 | debugs(20, 7, "cache_dir " << idx << " has: " << *e); |
249 | return e; | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
254 | debugs(20, 6, "none of " << Config.cacheSwap.n_configured << | |
255 | " cache_dirs have " << storeKeyText(key)); | |
256 | return nullptr; | |
257 | } | |
258 | ||
259 | void | |
260 | Store::Disks::init() | |
261 | { | |
262 | if (Config.Store.objectsPerBucket <= 0) | |
263 | fatal("'store_objects_per_bucket' should be larger than 0."); | |
264 | ||
265 | if (Config.Store.avgObjectSize <= 0) | |
266 | fatal("'store_avg_object_size' should be larger than 0."); | |
267 | ||
268 | /* Calculate size of hash table (maximum currently 64k buckets). */ | |
269 | /* this is very bogus, its specific to the any Store maintaining an | |
270 | * in-core index, not global */ | |
271 | size_t buckets = (Store::Root().maxSize() + Config.memMaxSize) / Config.Store.avgObjectSize; | |
c59baaa8 | 272 | debugs(20, Important(31), "Swap maxSize " << (Store::Root().maxSize() >> 10) << |
2745fea5 AR |
273 | " + " << ( Config.memMaxSize >> 10) << " KB, estimated " << buckets << " objects"); |
274 | buckets /= Config.Store.objectsPerBucket; | |
c59baaa8 | 275 | debugs(20, Important(32), "Target number of buckets: " << buckets); |
2745fea5 AR |
276 | /* ideally the full scan period should be configurable, for the |
277 | * moment it remains at approximately 24 hours. */ | |
278 | store_hash_buckets = storeKeyHashBuckets(buckets); | |
c59baaa8 EB |
279 | debugs(20, Important(33), "Using " << store_hash_buckets << " Store buckets"); |
280 | debugs(20, Important(34), "Max Mem size: " << ( Config.memMaxSize >> 10) << " KB" << | |
2745fea5 | 281 | (Config.memShared ? " [shared]" : "")); |
c59baaa8 | 282 | debugs(20, Important(35), "Max Swap size: " << (Store::Root().maxSize() >> 10) << " KB"); |
2745fea5 AR |
283 | |
284 | store_table = hash_create(storeKeyHashCmp, | |
285 | store_hash_buckets, storeKeyHashHash); | |
286 | ||
8ecbe78d EB |
287 | // Increment _before_ any possible storeRebuildComplete() calls so that |
288 | // storeRebuildComplete() can reliably detect when all disks are done. The | |
289 | // level is decremented in each corresponding storeRebuildComplete() call. | |
290 | StoreController::store_dirs_rebuilding += Config.cacheSwap.n_configured; | |
291 | ||
2745fea5 AR |
292 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { |
293 | /* this starts a search of the store dirs, loading their | |
294 | * index. under the new Store api this should be | |
295 | * driven by the StoreHashIndex, not by each store. | |
296 | * | |
297 | * That is, the HashIndex should perform a search of each dir it is | |
298 | * indexing to do the hash insertions. The search is then able to | |
299 | * decide 'from-memory', or 'from-clean-log' or 'from-dirty-log' or | |
300 | * 'from-no-log'. | |
301 | * | |
302 | * Step 1: make the store rebuilds use a search internally | |
303 | * Step 2: change the search logic to use the four modes described | |
304 | * above | |
305 | * Step 3: have the hash index walk the searches itself. | |
306 | */ | |
daed75a9 | 307 | if (Dir(i).active()) |
2745fea5 | 308 | store(i)->init(); |
8ecbe78d EB |
309 | else |
310 | storeRebuildComplete(nullptr); | |
2745fea5 AR |
311 | } |
312 | ||
313 | if (strcasecmp(Config.store_dir_select_algorithm, "round-robin") == 0) { | |
314 | storeDirSelectSwapDir = storeDirSelectSwapDirRoundRobin; | |
315 | debugs(47, DBG_IMPORTANT, "Using Round Robin store dir selection"); | |
316 | } else { | |
317 | storeDirSelectSwapDir = storeDirSelectSwapDirLeastLoad; | |
c59baaa8 | 318 | debugs(47, Important(36), "Using Least Load store dir selection"); |
2745fea5 AR |
319 | } |
320 | } | |
321 | ||
322 | uint64_t | |
323 | Store::Disks::maxSize() const | |
324 | { | |
325 | uint64_t result = 0; | |
326 | ||
327 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
daed75a9 | 328 | if (Dir(i).doReportStat()) |
2745fea5 AR |
329 | result += store(i)->maxSize(); |
330 | } | |
331 | ||
332 | return result; | |
333 | } | |
334 | ||
335 | uint64_t | |
336 | Store::Disks::minSize() const | |
337 | { | |
338 | uint64_t result = 0; | |
339 | ||
340 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
daed75a9 | 341 | if (Dir(i).doReportStat()) |
2745fea5 AR |
342 | result += store(i)->minSize(); |
343 | } | |
344 | ||
345 | return result; | |
346 | } | |
347 | ||
348 | uint64_t | |
349 | Store::Disks::currentSize() const | |
350 | { | |
351 | uint64_t result = 0; | |
352 | ||
353 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
daed75a9 | 354 | if (Dir(i).doReportStat()) |
2745fea5 AR |
355 | result += store(i)->currentSize(); |
356 | } | |
357 | ||
358 | return result; | |
359 | } | |
360 | ||
361 | uint64_t | |
362 | Store::Disks::currentCount() const | |
363 | { | |
364 | uint64_t result = 0; | |
365 | ||
366 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
daed75a9 | 367 | if (Dir(i).doReportStat()) |
2745fea5 AR |
368 | result += store(i)->currentCount(); |
369 | } | |
370 | ||
371 | return result; | |
372 | } | |
373 | ||
374 | int64_t | |
375 | Store::Disks::maxObjectSize() const | |
376 | { | |
5ca027f0 AR |
377 | return largestMaximumObjectSize; |
378 | } | |
379 | ||
380 | void | |
5d84beb5 | 381 | Store::Disks::configure() |
5ca027f0 | 382 | { |
5d84beb5 EB |
383 | if (!Config.cacheSwap.swapDirs) |
384 | Controller::store_dirs_rebuilding = 0; // nothing to index | |
385 | ||
5ca027f0 AR |
386 | largestMinimumObjectSize = -1; |
387 | largestMaximumObjectSize = -1; | |
388 | secondLargestMaximumObjectSize = -1; | |
2745fea5 | 389 | |
5d84beb5 EB |
390 | Config.cacheSwap.n_strands = 0; |
391 | ||
2745fea5 | 392 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { |
5d84beb5 EB |
393 | auto &disk = Dir(i); |
394 | if (disk.needsDiskStrand()) { | |
395 | assert(InDaemonMode()); | |
396 | // XXX: Do not pretend to support disk.disker changes during reconfiguration | |
397 | disk.disker = Config.workers + (++Config.cacheSwap.n_strands); | |
398 | } | |
399 | ||
5ca027f0 AR |
400 | if (!disk.active()) |
401 | continue; | |
402 | ||
403 | if (disk.minObjectSize() > largestMinimumObjectSize) | |
404 | largestMinimumObjectSize = disk.minObjectSize(); | |
405 | ||
406 | const auto diskMaxObjectSize = disk.maxObjectSize(); | |
407 | if (diskMaxObjectSize > largestMaximumObjectSize) { | |
408 | if (largestMaximumObjectSize >= 0) // was set | |
409 | secondLargestMaximumObjectSize = largestMaximumObjectSize; | |
410 | largestMaximumObjectSize = diskMaxObjectSize; | |
411 | } | |
2745fea5 | 412 | } |
5ca027f0 | 413 | } |
2745fea5 | 414 | |
5d84beb5 EB |
415 | void |
416 | Store::Disks::Parse(DiskConfig &swap) | |
417 | { | |
418 | const auto typeStr = ConfigParser::NextToken(); | |
419 | if (!typeStr) | |
420 | throw TextException("missing cache_dir parameter: storage type", Here()); | |
421 | ||
422 | const auto pathStr = ConfigParser::NextToken(); | |
423 | if (!pathStr) | |
424 | throw TextException("missing cache_dir parameter: directory name", Here()); | |
425 | ||
426 | const auto fs = StoreFileSystem::FindByType(typeStr); | |
427 | if (!fs) { | |
428 | debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: This proxy does not support the '" << typeStr << "' cache type. Ignoring."); | |
429 | return; | |
430 | } | |
431 | ||
432 | const auto fsType = fs->type(); | |
433 | ||
434 | // check for the existing cache_dir | |
435 | // XXX: This code mistreats duplicated cache_dir entries (that should be fatal). | |
436 | for (int i = 0; i < swap.n_configured; ++i) { | |
437 | auto &disk = Dir(i); | |
438 | if ((strcasecmp(pathStr, disk.path)) == 0) { | |
439 | /* this is specific to on-fs Stores. The right | |
440 | * way to handle this is probably to have a mapping | |
441 | * from paths to stores, and have on-fs stores | |
442 | * register with that, and lookip in that in their | |
443 | * own setup logic. RBC 20041225. TODO. | |
444 | */ | |
445 | ||
446 | if (strcmp(disk.type(), fsType) == 0) | |
447 | disk.reconfigure(); | |
448 | else | |
449 | debugs(3, DBG_CRITICAL, "ERROR: Can't change type of existing cache_dir " << | |
450 | disk.type() << " " << disk.path << " to " << fsType << ". Restart required"); | |
451 | ||
452 | return; | |
453 | } | |
454 | } | |
455 | ||
456 | const int cacheDirCountLimit = 64; // StoreEntry::swap_dirn is a signed 7-bit integer | |
457 | if (swap.n_configured >= cacheDirCountLimit) | |
458 | throw TextException(ToSBuf("Squid cannot handle more than ", cacheDirCountLimit, " cache_dir directives"), Here()); | |
459 | ||
460 | // create a new cache_dir | |
461 | allocate_new_swapdir(swap); | |
462 | swap.swapDirs[swap.n_configured] = fs->createSwapDir(); | |
463 | auto &disk = Dir(swap.n_configured); | |
464 | disk.parse(swap.n_configured, pathStr); | |
465 | ++swap.n_configured; | |
466 | } | |
467 | ||
468 | void | |
469 | Store::Disks::Dump(const DiskConfig &swap, StoreEntry &entry, const char *name) | |
470 | { | |
70ac5b29 | 471 | for (int i = 0; i < swap.n_configured; ++i) { |
472 | const auto &disk = Dir(i); | |
473 | storeAppendPrintf(&entry, "%s %s %s", name, disk.type(), disk.path); | |
474 | disk.dump(entry); | |
475 | storeAppendPrintf(&entry, "\n"); | |
476 | } | |
5d84beb5 EB |
477 | } |
478 | ||
5ca027f0 AR |
479 | int64_t |
480 | Store::Disks::accumulateMore(const StoreEntry &entry) const | |
481 | { | |
482 | const auto accumulated = entry.mem_obj->availableForSwapOut(); | |
483 | ||
82bee387 | 484 | /* |
5ca027f0 AR |
485 | * Keep accumulating more bytes until the set of disks eligible to accept |
486 | * the entry becomes stable, and, hence, accumulating more is not going to | |
487 | * affect the cache_dir selection. A stable set is usually reached | |
488 | * immediately (or soon) because most configurations either do not use | |
489 | * cache_dirs with explicit min-size/max-size limits or use the same | |
490 | * max-size limit for all cache_dirs (and low min-size limits). | |
491 | */ | |
492 | ||
493 | // Can the set of min-size cache_dirs accepting this entry change? | |
494 | if (accumulated < largestMinimumObjectSize) | |
495 | return largestMinimumObjectSize - accumulated; | |
496 | ||
497 | // Can the set of max-size cache_dirs accepting this entry change | |
498 | // (other than when the entry exceeds the largest maximum; see below)? | |
499 | if (accumulated <= secondLargestMaximumObjectSize) | |
500 | return secondLargestMaximumObjectSize - accumulated + 1; | |
501 | ||
82bee387 | 502 | /* |
5ca027f0 AR |
503 | * Checking largestMaximumObjectSize instead eliminates the risk of starting |
504 | * to swap out an entry that later grows too big, but also implies huge | |
505 | * accumulation in most environments. Accumulating huge entries not only | |
506 | * consumes lots of RAM but also creates a burst of doPages() write requests | |
507 | * that overwhelm the disk. To avoid these problems, we take the risk and | |
508 | * allow swap out now. The disk will quit swapping out if the entry | |
509 | * eventually grows too big for its selected cache_dir. | |
510 | */ | |
511 | debugs(20, 3, "no: " << accumulated << '>' << | |
512 | secondLargestMaximumObjectSize << ',' << largestMinimumObjectSize); | |
513 | return 0; | |
2745fea5 AR |
514 | } |
515 | ||
516 | void | |
517 | Store::Disks::getStats(StoreInfoStats &stats) const | |
518 | { | |
519 | // accumulate per-disk cache stats | |
520 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
521 | StoreInfoStats dirStats; | |
522 | store(i)->getStats(dirStats); | |
523 | stats += dirStats; | |
524 | } | |
525 | ||
526 | // common to all disks | |
527 | stats.swap.open_disk_fd = store_open_disk_fd; | |
528 | ||
529 | // memory cache stats are collected in StoreController::getStats(), for now | |
530 | } | |
531 | ||
532 | void | |
533 | Store::Disks::stat(StoreEntry & output) const | |
534 | { | |
535 | int i; | |
536 | ||
537 | /* Now go through each store, calling its stat routine */ | |
538 | ||
539 | for (i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
540 | storeAppendPrintf(&output, "\n"); | |
541 | store(i)->stat(output); | |
542 | } | |
543 | } | |
544 | ||
545 | void | |
546 | Store::Disks::reference(StoreEntry &e) | |
547 | { | |
548 | e.disk().reference(e); | |
549 | } | |
550 | ||
551 | bool | |
552 | Store::Disks::dereference(StoreEntry &e) | |
553 | { | |
554 | return e.disk().dereference(e); | |
555 | } | |
556 | ||
abf396ec AR |
557 | void |
558 | Store::Disks::updateHeaders(StoreEntry *e) | |
559 | { | |
560 | Must(e); | |
561 | return e->disk().updateHeaders(e); | |
562 | } | |
563 | ||
2745fea5 AR |
564 | void |
565 | Store::Disks::maintain() | |
566 | { | |
567 | int i; | |
568 | /* walk each fs */ | |
569 | ||
570 | for (i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
2f8abb64 | 571 | /* XXX FixMe: This should be done "in parallel" on the different |
2745fea5 AR |
572 | * cache_dirs, not one at a time. |
573 | */ | |
574 | /* call the maintain function .. */ | |
575 | store(i)->maintain(); | |
576 | } | |
577 | } | |
578 | ||
579 | void | |
580 | Store::Disks::sync() | |
581 | { | |
582 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) | |
583 | store(i)->sync(); | |
584 | } | |
585 | ||
7d84d4ca | 586 | void |
4310f8b0 EB |
587 | Store::Disks::evictCached(StoreEntry &e) { |
588 | if (e.hasDisk()) { | |
589 | // TODO: move into Fs::Ufs::UFSSwapDir::evictCached() | |
590 | if (!EBIT_TEST(e.flags, KEY_PRIVATE)) { | |
591 | // log before evictCached() below may clear hasDisk() | |
592 | storeDirSwapLog(&e, SWAP_LOG_DEL); | |
593 | } | |
594 | ||
595 | e.disk().evictCached(e); | |
596 | return; | |
597 | } | |
598 | ||
599 | if (const auto key = e.publicKey()) | |
600 | evictIfFound(key); | |
2745fea5 AR |
601 | } |
602 | ||
7d84d4ca | 603 | void |
4310f8b0 EB |
604 | Store::Disks::evictIfFound(const cache_key *key) |
605 | { | |
606 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
daed75a9 EB |
607 | if (Dir(i).active()) |
608 | Dir(i).evictIfFound(key); | |
4310f8b0 | 609 | } |
2745fea5 AR |
610 | } |
611 | ||
612 | bool | |
778610b5 | 613 | Store::Disks::anchorToCache(StoreEntry &entry) |
2745fea5 | 614 | { |
778610b5 AR |
615 | if (entry.hasDisk()) |
616 | return true; // already anchored | |
617 | ||
2745fea5 AR |
618 | if (const int cacheDirs = Config.cacheSwap.n_configured) { |
619 | // ask each cache_dir until the entry is found; use static starting | |
620 | // point to avoid asking the same subset of disks more often | |
621 | // TODO: coordinate with put() to be able to guess the right disk often | |
622 | static int idx = 0; | |
623 | for (int n = 0; n < cacheDirs; ++n) { | |
624 | idx = (idx + 1) % cacheDirs; | |
daed75a9 | 625 | SwapDir &sd = Dir(idx); |
2745fea5 AR |
626 | if (!sd.active()) |
627 | continue; | |
628 | ||
778610b5 | 629 | if (sd.anchorToCache(entry)) { |
4310f8b0 | 630 | debugs(20, 3, "cache_dir " << idx << " anchors " << entry); |
2745fea5 AR |
631 | return true; |
632 | } | |
633 | } | |
634 | } | |
635 | ||
636 | debugs(20, 4, "none of " << Config.cacheSwap.n_configured << | |
4310f8b0 | 637 | " cache_dirs have " << entry); |
2745fea5 AR |
638 | return false; |
639 | } | |
640 | ||
641 | bool | |
4310f8b0 | 642 | Store::Disks::updateAnchored(StoreEntry &entry) |
2745fea5 | 643 | { |
4310f8b0 | 644 | return entry.hasDisk() && |
5d84beb5 | 645 | entry.disk().updateAnchored(entry); |
2745fea5 AR |
646 | } |
647 | ||
1a210de4 | 648 | bool |
daed75a9 | 649 | Store::Disks::SmpAware() |
1a210de4 EB |
650 | { |
651 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) { | |
652 | // A mix is not supported, but we conservatively check every | |
653 | // dir because features like collapsed revalidation should | |
654 | // currently be disabled if any dir is SMP-aware | |
daed75a9 | 655 | if (Dir(i).smpAware()) |
1a210de4 EB |
656 | return true; |
657 | } | |
658 | return false; | |
659 | } | |
660 | ||
5d84beb5 EB |
661 | SwapDir * |
662 | Store::Disks::SelectSwapDir(const StoreEntry *e) | |
663 | { | |
664 | return storeDirSelectSwapDir(e); | |
665 | } | |
666 | ||
4310f8b0 EB |
667 | bool |
668 | Store::Disks::hasReadableEntry(const StoreEntry &e) const | |
669 | { | |
670 | for (int i = 0; i < Config.cacheSwap.n_configured; ++i) | |
daed75a9 | 671 | if (Dir(i).active() && Dir(i).hasReadableEntry(e)) |
4310f8b0 EB |
672 | return true; |
673 | return false; | |
674 | } | |
2745fea5 AR |
675 | |
676 | void | |
677 | storeDirOpenSwapLogs() | |
678 | { | |
679 | for (int dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) | |
5d84beb5 | 680 | SwapDirByIndex(dirn).openLog(); |
2745fea5 AR |
681 | } |
682 | ||
683 | void | |
684 | storeDirCloseSwapLogs() | |
685 | { | |
686 | for (int dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) | |
5d84beb5 | 687 | SwapDirByIndex(dirn).closeLog(); |
2745fea5 AR |
688 | } |
689 | ||
024aeeee | 690 | /** |
2745fea5 AR |
691 | * storeDirWriteCleanLogs |
692 | * | |
693 | * Writes a "clean" swap log file from in-memory metadata. | |
694 | * This is a rewrite of the original function to troll each | |
695 | * StoreDir and write the logs, and flush at the end of | |
696 | * the run. Thanks goes to Eric Stern, since this solution | |
697 | * came out of his COSS code. | |
698 | */ | |
699 | int | |
700 | storeDirWriteCleanLogs(int reopen) | |
701 | { | |
aee3523a | 702 | const StoreEntry *e = nullptr; |
2745fea5 AR |
703 | int n = 0; |
704 | ||
705 | struct timeval start; | |
706 | double dt; | |
2745fea5 AR |
707 | int dirn; |
708 | int notdone = 1; | |
709 | ||
710 | // Check for store_dirs_rebuilding because fatal() often calls us in early | |
711 | // initialization phases, before store log is initialized and ready. Also, | |
7c44eb08 | 712 | // some stores do not support log cleanup during Store rebuilding. |
2745fea5 | 713 | if (StoreController::store_dirs_rebuilding) { |
c59baaa8 EB |
714 | debugs(20, Important(37), "Not currently OK to rewrite swap log."); |
715 | debugs(20, Important(38), "storeDirWriteCleanLogs: Operation aborted."); | |
2745fea5 AR |
716 | return 0; |
717 | } | |
718 | ||
c59baaa8 | 719 | debugs(20, Important(39), "storeDirWriteCleanLogs: Starting..."); |
2745fea5 AR |
720 | getCurrentTime(); |
721 | start = current_time; | |
722 | ||
723 | for (dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) { | |
5d84beb5 | 724 | auto &sd = SwapDirByIndex(dirn); |
2745fea5 | 725 | |
5d84beb5 | 726 | if (sd.writeCleanStart() < 0) { |
d816f28d | 727 | debugs(20, DBG_IMPORTANT, "ERROR: log.clean.start() failed for dir #" << sd.index); |
2745fea5 AR |
728 | continue; |
729 | } | |
730 | } | |
731 | ||
732 | /* | |
733 | * This may look inefficient as CPU wise it is more efficient to do this | |
734 | * sequentially, but I/O wise the parallellism helps as it allows more | |
735 | * hdd spindles to be active. | |
736 | */ | |
737 | while (notdone) { | |
738 | notdone = 0; | |
739 | ||
740 | for (dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) { | |
5d84beb5 | 741 | auto &sd = SwapDirByIndex(dirn); |
2745fea5 | 742 | |
5d84beb5 | 743 | if (!sd.cleanLog) |
2745fea5 AR |
744 | continue; |
745 | ||
5d84beb5 | 746 | e = sd.cleanLog->nextEntry(); |
2745fea5 AR |
747 | |
748 | if (!e) | |
749 | continue; | |
750 | ||
751 | notdone = 1; | |
752 | ||
5d84beb5 | 753 | if (!sd.canLog(*e)) |
2745fea5 AR |
754 | continue; |
755 | ||
5d84beb5 | 756 | sd.cleanLog->write(*e); |
2745fea5 AR |
757 | |
758 | if ((++n & 0xFFFF) == 0) { | |
759 | getCurrentTime(); | |
760 | debugs(20, DBG_IMPORTANT, " " << std::setw(7) << n << | |
761 | " entries written so far."); | |
762 | } | |
763 | } | |
764 | } | |
765 | ||
766 | /* Flush */ | |
767 | for (dirn = 0; dirn < Config.cacheSwap.n_configured; ++dirn) | |
5d84beb5 | 768 | SwapDirByIndex(dirn).writeCleanDone(); |
2745fea5 AR |
769 | |
770 | if (reopen) | |
771 | storeDirOpenSwapLogs(); | |
772 | ||
773 | getCurrentTime(); | |
774 | ||
775 | dt = tvSubDsec(start, current_time); | |
776 | ||
c59baaa8 EB |
777 | debugs(20, Important(40), " Finished. Wrote " << n << " entries."); |
778 | debugs(20, Important(41), " Took "<< std::setw(3) << std::setprecision(2) << dt << | |
2745fea5 AR |
779 | " seconds ("<< std::setw(6) << ((double) n / (dt > 0.0 ? dt : 1.0)) << " entries/sec)."); |
780 | ||
781 | return n; | |
782 | } | |
783 | ||
784 | /* Globals that should be converted to static Store::Disks methods */ | |
785 | ||
786 | void | |
5d84beb5 | 787 | allocate_new_swapdir(Store::DiskConfig &swap) |
2745fea5 | 788 | { |
5d84beb5 EB |
789 | if (!swap.swapDirs) { |
790 | swap.n_allocated = 4; | |
791 | swap.swapDirs = new SwapDir::Pointer[swap.n_allocated]; | |
2745fea5 AR |
792 | } |
793 | ||
5d84beb5 EB |
794 | if (swap.n_allocated == swap.n_configured) { |
795 | swap.n_allocated <<= 1; | |
796 | const auto tmp = new SwapDir::Pointer[swap.n_allocated]; | |
797 | for (int i = 0; i < swap.n_configured; ++i) { | |
798 | tmp[i] = swap.swapDirs[i]; | |
b56b37cf | 799 | } |
5d84beb5 EB |
800 | delete[] swap.swapDirs; |
801 | swap.swapDirs = tmp; | |
2745fea5 AR |
802 | } |
803 | } | |
804 | ||
805 | void | |
806 | free_cachedir(Store::DiskConfig *swap) | |
807 | { | |
2745fea5 AR |
808 | /* DON'T FREE THESE FOR RECONFIGURE */ |
809 | ||
810 | if (reconfiguring) | |
811 | return; | |
812 | ||
b56b37cf AJ |
813 | /* TODO XXX this lets the swapdir free resources asynchronously |
814 | * swap->swapDirs[i]->deactivate(); | |
815 | * but there may be such a means already. | |
816 | * RBC 20041225 | |
817 | */ | |
2745fea5 | 818 | |
b56b37cf AJ |
819 | // only free's the array memory itself |
820 | // the SwapDir objects may remain (ref-counted) | |
821 | delete[] swap->swapDirs; | |
822 | swap->swapDirs = nullptr; | |
2745fea5 AR |
823 | swap->n_allocated = 0; |
824 | swap->n_configured = 0; | |
825 | } | |
826 | ||
827 | /* Globals that should be moved to some Store::UFS-specific logging module */ | |
828 | ||
024aeeee | 829 | /** |
2745fea5 AR |
830 | * An entry written to the swap log MUST have the following |
831 | * properties. | |
832 | * 1. It MUST be a public key. It does no good to log | |
833 | * a public ADD, change the key, then log a private | |
834 | * DEL. So we need to log a DEL before we change a | |
835 | * key from public to private. | |
836 | * 2. It MUST have a valid (> -1) swap_filen. | |
837 | */ | |
838 | void | |
839 | storeDirSwapLog(const StoreEntry * e, int op) | |
840 | { | |
841 | assert (e); | |
842 | assert(!EBIT_TEST(e->flags, KEY_PRIVATE)); | |
4310f8b0 | 843 | assert(e->hasDisk()); |
2745fea5 AR |
844 | /* |
845 | * icons and such; don't write them to the swap log | |
846 | */ | |
847 | ||
848 | if (EBIT_TEST(e->flags, ENTRY_SPECIAL)) | |
849 | return; | |
850 | ||
851 | assert(op > SWAP_LOG_NOP && op < SWAP_LOG_MAX); | |
852 | ||
853 | debugs(20, 3, "storeDirSwapLog: " << | |
854 | swap_log_op_str[op] << " " << | |
855 | e->getMD5Text() << " " << | |
856 | e->swap_dirn << " " << | |
a4dd5bfa | 857 | asHex(e->swap_filen).upperCase().minDigits(8)); |
2745fea5 | 858 | |
5d84beb5 | 859 | e->disk().logEntry(*e, op); |
2745fea5 | 860 | } |
7d84d4ca | 861 |