| /** @file | |
| Main program file for Cache Tool. | |
| @section license License | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #include <algorithm> | |
| #include <iostream> | |
| #include <list> | |
| #include <memory> | |
| #include <vector> | |
| #include <map> | |
| #include <system_error> | |
| #include <fcntl.h> | |
| #include <cctype> | |
| #include <cstring> | |
| #include <vector> | |
| #include <unordered_set> | |
| #include <ctime> | |
| #include <bitset> | |
| #include <cinttypes> | |
| #include "tscore/ink_memory.h" | |
| #include "tscore/ink_file.h" | |
| #include "tscore/CryptoHash.h" | |
| #include "tscore/ArgParser.h" | |
| #include <thread> | |
| #include "CacheDefs.h" | |
| #include "CacheScan.h" | |
| using swoc::Errata; | |
| using swoc::MemSpan; | |
| using ts::make_errno_code; | |
| using ts::Bytes; | |
| using ts::CacheDirEntry; | |
| using ts::CacheStoreBlocks; | |
| using ts::CacheStripeBlocks; | |
| using ts::CacheStripeDescriptor; | |
| using ts::Doc; | |
| using ts::Megabytes; | |
| using ts::StripeMeta; | |
| enum { SILENT = 0, NORMAL, VERBOSE } Verbosity = NORMAL; | |
| extern int cache_config_min_average_object_size; | |
| extern CacheStoreBlocks Vol_hash_alloc_size; | |
| extern int OPEN_RW_FLAG; | |
| const Bytes ts::CacheSpan::OFFSET{CacheStoreBlocks{1}}; | |
| swoc::file::path SpanFile; | |
| swoc::file::path VolumeFile; | |
| ts::ArgParser parser; | |
| Errata err; | |
| namespace ct | |
| { | |
| /* --------------------------------------------------------------------------------------- */ | |
| /// A live volume. | |
| /// Volume data based on data from loaded spans. | |
| struct Volume { | |
| int _idx; ///< Volume index. | |
| CacheStoreBlocks _size; ///< Amount of storage allocated. | |
| std::vector<StripeSM *> _stripes; | |
| /// Remove all data related to @a span. | |
| // void clearSpan(Span *span); | |
| /// Remove all allocated space and stripes. | |
| void clear(); | |
| }; | |
| #if 0 | |
| void | |
| Volume::clearSpan(Span* span) | |
| { | |
| auto spot = std::remove_if(_stripes.begin(), _stripes.end(), [span,this](StripeSM* stripe) { return stripe->_span == span ? ( this->_size -= stripe->_len , true ) : false; }); | |
| _stripes.erase(spot, _stripes.end()); | |
| } | |
| #endif | |
| void | |
| Volume::clear() | |
| { | |
| _size.assign(0); | |
| _stripes.clear(); | |
| } | |
| /* --------------------------------------------------------------------------------------- */ | |
| /// Data parsed from the volume config file. | |
| struct VolumeConfig { | |
| Errata load(swoc::file::path const &path); | |
| /// Data direct from the config file. | |
| struct Data { | |
| int _idx = 0; ///< Volume index. | |
| int _percent = 0; ///< Size if specified as a percent. | |
| Megabytes _size{0}; ///< Size if specified as an absolute. | |
| CacheStripeBlocks _alloc; ///< Allocation size. | |
| // Methods handy for parsing | |
| bool | |
| hasSize() const | |
| { | |
| return _percent > 0 || _size > 0; | |
| } | |
| bool | |
| hasIndex() const | |
| { | |
| return _idx > 0; | |
| } | |
| }; | |
| std::vector<Data> _volumes; | |
| using iterator = std::vector<Data>::iterator; | |
| using const_iterator = std::vector<Data>::const_iterator; | |
| iterator | |
| begin() | |
| { | |
| return _volumes.begin(); | |
| } | |
| iterator | |
| end() | |
| { | |
| return _volumes.end(); | |
| } | |
| #if 0 | |
| const_iterator | |
| begin() const | |
| { | |
| return _volumes.begin(); | |
| } | |
| const_iterator | |
| end() const | |
| { | |
| return _volumes.end(); | |
| } | |
| #endif | |
| // Errata validatePercentAllocation(); | |
| void convertToAbsolute(const ts::CacheStripeBlocks &total_span_size); | |
| }; | |
| #if 0 | |
| Errata | |
| VolumeConfig::validatePercentAllocation() | |
| { | |
| Errata zret; | |
| int n = 0; | |
| for (auto &vol : _volumes) | |
| n += vol._percent; | |
| if (n > 100) | |
| zret.push(0, 10, "Volume percent allocation ", n, " is more than 100%"); | |
| return zret; | |
| } | |
| #endif | |
| void | |
| VolumeConfig::convertToAbsolute(const ts::CacheStripeBlocks &n) | |
| { | |
| for (auto &vol : _volumes) { | |
| if (vol._percent) { | |
| vol._alloc.assign((n.count() * vol._percent + 99) / 100); | |
| } else { | |
| vol._alloc = round_up(vol._size); | |
| } | |
| } | |
| } | |
| /* --------------------------------------------------------------------------------------- */ | |
| struct Cache { | |
| ~Cache(); | |
| Errata loadSpan(swoc::file::path const &path); | |
| Errata loadSpanConfig(swoc::file::path const &path); | |
| Errata loadSpanDirect(swoc::file::path const &path, int vol_idx = -1, const Bytes &size = Bytes(-1)); | |
| Errata loadURLs(swoc::file::path const &path); | |
| Errata allocStripe(Span *span, int vol_idx, const CacheStripeBlocks &len); | |
| /// Change the @a span to have a single, unused stripe occupying the entire @a span. | |
| // void clearSpan(Span *span); | |
| /// Clear all allocated space. | |
| void clearAllocation(); | |
| enum class SpanDumpDepth { SPAN, STRIPE, DIRECTORY }; | |
| void dumpSpans(SpanDumpDepth depth); | |
| void dumpVolumes(); | |
| void build_stripe_hash_table(); | |
| StripeSM *key_to_stripe(CryptoHash *key, const char *hostname, int host_len); | |
| // ts::CacheStripeBlocks calcTotalSpanPhysicalSize(); | |
| ts::CacheStripeBlocks calcTotalSpanConfiguredSize(); | |
| std::list<std::unique_ptr<Span>> _spans; | |
| std::map<int, Volume> _volumes; | |
| std::vector<StripeSM *> globalVec_stripe; | |
| std::unordered_set<ts::CacheURL *> URLset; | |
| ats_scoped_mem<unsigned short> stripes_hash_table; | |
| }; | |
| Errata | |
| Cache::allocStripe(Span *span, int vol_idx, const CacheStripeBlocks &len) | |
| { | |
| auto rv = span->allocStripe(vol_idx, len); | |
| std::cout << span->_path.string() << ":" << vol_idx << std::endl; | |
| if (rv.is_ok()) { | |
| _volumes[vol_idx]._stripes.push_back(rv); | |
| } | |
| return std::move(rv.errata()); | |
| } | |
| #if 0 | |
| void | |
| Cache::clearSpan(Span* span) | |
| { | |
| for ( auto& item : _volumes ) item.second.clearSpan(span); | |
| span->clear(); | |
| } | |
| #endif | |
| void | |
| Cache::clearAllocation() | |
| { | |
| for (auto &span : _spans) { | |
| span->clear(); | |
| } | |
| for (auto &item : _volumes) { | |
| item.second.clear(); | |
| } | |
| } | |
| /* --------------------------------------------------------------------------------------- */ | |
| /// Temporary structure used for doing allocation computations. | |
| class VolumeAllocator | |
| { | |
| /// Working struct that tracks allocation information. | |
| struct V { | |
| VolumeConfig::Data const &_config; ///< Configuration instance. | |
| CacheStripeBlocks _size; ///< Current actual size. | |
| int64_t _deficit; ///< fractional deficit | |
| int64_t _shares; ///< relative amount of free space to allocate | |
| V(VolumeConfig::Data const &config, const CacheStripeBlocks &size, int64_t deficit = 0, int64_t shares = 0) | |
| : _config(config), _size(size), _deficit(deficit), _shares(shares) | |
| { | |
| } | |
| V(const V &v) = default; | |
| V & | |
| operator=(V const &that) | |
| { | |
| new (this) V(that._config, that._size, that._deficit, that._shares); | |
| return *this; | |
| } | |
| }; | |
| using AV = std::vector<V>; | |
| AV _av; ///< Working vector of volume data. | |
| Cache _cache; ///< Current state. | |
| VolumeConfig _vols; ///< Configuration state. | |
| public: | |
| VolumeAllocator(); | |
| Errata load(swoc::file::path const &spanFile, swoc::file::path const &volumeFile); | |
| Errata fillEmptySpans(); | |
| Errata fillAllSpans(); | |
| Errata allocateSpan(swoc::file::path const &spanFile); | |
| void dumpVolumes(); | |
| protected: | |
| /// Update the allocation for a span. | |
| Errata allocateFor(Span &span); | |
| }; | |
| VolumeAllocator::VolumeAllocator() = default; | |
| Errata | |
| VolumeAllocator::load(swoc::file::path const &spanFile, swoc::file::path const &volumeFile) | |
| { | |
| Errata zret; | |
| if (volumeFile.empty()) { | |
| zret.note("Volume config file not set"); | |
| } | |
| if (spanFile.empty()) { | |
| zret.note("Span file not set"); | |
| } | |
| if (zret) { | |
| zret = _vols.load(volumeFile); | |
| if (zret) { | |
| zret = _cache.loadSpan(spanFile); | |
| if (zret) { | |
| CacheStripeBlocks total = _cache.calcTotalSpanConfiguredSize(); | |
| _vols.convertToAbsolute(total); | |
| for (auto &vol : _vols) { | |
| CacheStripeBlocks size(0); | |
| auto spot = _cache._volumes.find(vol._idx); | |
| if (spot != _cache._volumes.end()) { | |
| size = round_down(spot->second._size); | |
| } | |
| _av.push_back({vol, size, 0, 0}); | |
| } | |
| } | |
| } | |
| } | |
| return zret; | |
| } | |
| void | |
| VolumeAllocator::dumpVolumes() | |
| { | |
| _cache.dumpVolumes(); | |
| } | |
| Errata | |
| VolumeAllocator::fillEmptySpans() | |
| { | |
| Errata zret; | |
| // Walk the spans, skipping ones that are not empty. | |
| for (auto &span : _cache._spans) { | |
| if (span->isEmpty()) { | |
| this->allocateFor(*span); | |
| } | |
| } | |
| return zret; | |
| } | |
| Errata | |
| VolumeAllocator::allocateSpan(swoc::file::path const &input_file_path) | |
| { | |
| Errata zret; | |
| for (auto &span : _cache._spans) { | |
| if (span->_path.view() == input_file_path.view()) { | |
| std::cout << "===============================" << std::endl; | |
| if (span->_header) { | |
| zret.note("Disk already initialized with valid header"); | |
| } else { | |
| this->allocateFor(*span); | |
| span->updateHeader(); | |
| for (auto &strp : span->_stripes) { | |
| strp->updateHeaderFooter(); | |
| } | |
| } | |
| } | |
| } | |
| for (auto &_v : _av) { | |
| std::cout << _v._size << std::endl; | |
| } | |
| return zret; | |
| } | |
| Errata | |
| VolumeAllocator::fillAllSpans() | |
| { | |
| Errata zret; | |
| // clear all current volume allocations. | |
| for (auto &v : _av) { | |
| v._size.assign(0); | |
| } | |
| // Allocate for each span, clearing as it goes. | |
| _cache.clearAllocation(); | |
| for (auto &span : _cache._spans) { | |
| this->allocateFor(*span); | |
| } | |
| return zret; | |
| } | |
| Errata | |
| VolumeAllocator::allocateFor(Span &span) | |
| { | |
| Errata zret; | |
| /// Scaling factor for shares, effectively the accuracy. | |
| static const int64_t SCALE = 1000; | |
| int64_t total_shares = 0; | |
| if (Verbosity >= NORMAL) { | |
| std::cout << "Allocating " << CacheStripeBlocks(round_down(span._len)).count() << " stripe blocks from span " | |
| << span._path.string() << std::endl; | |
| } | |
| // Walk the volumes and get the relative allocations. | |
| for (auto &v : _av) { | |
| auto delta = v._config._alloc - v._size; | |
| if (delta > 0) { | |
| v._deficit = (delta.count() * SCALE) / v._config._alloc.count(); | |
| v._shares = delta.count() * v._deficit; | |
| total_shares += v._shares; | |
| } else { | |
| v._shares = 0; | |
| } | |
| } | |
| assert(total_shares != 0); | |
| // Now allocate blocks. | |
| CacheStripeBlocks span_blocks(round_down(span._free_space)); | |
| CacheStripeBlocks span_used{0}; | |
| // sort by deficit so least relatively full volumes go first. | |
| std::sort(_av.begin(), _av.end(), [](V const &lhs, V const &rhs) { return lhs._deficit > rhs._deficit; }); | |
| for (auto &v : _av) { | |
| if (v._shares) { | |
| CacheStripeBlocks n{(((span_blocks - span_used).count() * v._shares) + total_shares - 1) / total_shares}; | |
| CacheStripeBlocks delta{v._config._alloc - v._size}; | |
| // Not sure why this is needed. But a large and empty volume can dominate the shares | |
| // enough to get more than it actually needs if the other volume are relative small or full. | |
| // I need to do more math to see if the weighting can be adjusted to not have this happen. | |
| n = std::min(n, delta); | |
| v._size += n; | |
| span_used += n; | |
| total_shares -= v._shares; | |
| Errata z = _cache.allocStripe(&span, v._config._idx, round_up(n)); | |
| if (Verbosity >= NORMAL) { | |
| std::cout << " " << n << " to volume " << v._config._idx << std::endl; | |
| } | |
| if (!z) { | |
| std::cout << z; | |
| } | |
| } | |
| } | |
| if (Verbosity >= NORMAL) { | |
| std::cout << " Total " << span_used << std::endl; | |
| } | |
| if (OPEN_RW_FLAG) { | |
| if (Verbosity >= NORMAL) { | |
| std::cout << " Updating Header ... "; | |
| } | |
| zret = span.updateHeader(); | |
| } | |
| _cache.dumpVolumes(); // debug | |
| if (Verbosity >= NORMAL) { | |
| if (zret) { | |
| std::cout << " Done" << std::endl; | |
| } else { | |
| std::cout << " Error" << std::endl << zret; | |
| } | |
| } | |
| return zret; | |
| } | |
| /* --------------------------------------------------------------------------------------- */ | |
| Errata | |
| Cache::loadSpan(swoc::file::path const &path) | |
| { | |
| Errata zret; | |
| std::error_code ec; | |
| auto fs = swoc::file::status(path, ec); | |
| if (path.empty()) { | |
| zret = Errata(make_errno_code(EINVAL), "A span file specified by --spans is required"); | |
| } else if (!swoc::file::is_readable(path)) { | |
| zret = Errata(make_errno_code(EPERM), R"("{}" is not readable.)", path); | |
| } else if (swoc::file::is_regular_file(fs)) { | |
| zret = this->loadSpanConfig(path); | |
| } else { | |
| zret = this->loadSpanDirect(path); | |
| } | |
| return zret; | |
| } | |
| Errata | |
| Cache::loadSpanDirect(swoc::file::path const &path, int vol_idx, [[maybe_unused]] const Bytes &size) | |
| { | |
| Errata zret; | |
| std::unique_ptr<Span> span(new Span(path)); | |
| zret = span->load(); | |
| if (zret) { | |
| if (span->_header) { | |
| int nspb = span->_header->num_diskvol_blks; | |
| for (auto i = 0; i < nspb; ++i) { | |
| ts::CacheStripeDescriptor &raw = span->_header->stripes[i]; | |
| StripeSM *stripe = new StripeSM(span.get(), raw.offset, raw.len); | |
| stripe->_idx = i; | |
| if (raw.free == 0) { | |
| stripe->_vol_idx = raw.vol_idx; | |
| stripe->_type = raw.type; | |
| _volumes[stripe->_vol_idx]._stripes.push_back(stripe); | |
| _volumes[stripe->_vol_idx]._size += stripe->_len; | |
| stripe->vol_init_data(); | |
| } else { | |
| span->_free_space += stripe->_len; | |
| } | |
| span->_stripes.push_back(stripe); | |
| globalVec_stripe.push_back(stripe); | |
| } | |
| span->_vol_idx = vol_idx; | |
| } else { | |
| span->clear(); | |
| } | |
| _spans.emplace_back(std::move(span.release())); | |
| } | |
| return zret; | |
| } | |
| Errata | |
| Cache::loadSpanConfig(swoc::file::path const &path) | |
| { | |
| static const swoc::TextView TAG_ID("id"); | |
| static const swoc::TextView TAG_VOL("volume"); | |
| Errata zret; | |
| std::error_code ec; | |
| std::string load_content = swoc::file::load(path, ec); | |
| if (ec.value() == 0) { | |
| swoc::TextView content(load_content); | |
| while (content) { | |
| swoc::TextView line = content.take_prefix_at('\n'); | |
| line.ltrim_if(&isspace); | |
| if (line.empty() || '#' == *line) { | |
| continue; | |
| } | |
| swoc::TextView localpath = line.take_prefix_if(&isspace); | |
| if (localpath) { | |
| // After this the line is [size] [id=string] [volume=#] | |
| while (line) { | |
| swoc::TextView value(line.take_prefix_if(&isspace)); | |
| if (value) { | |
| swoc::TextView tag(value.take_prefix_at('=')); | |
| if (!tag) { // must be the size | |
| } else if (0 == strcasecmp(tag, TAG_ID)) { | |
| } else if (0 == strcasecmp(tag, TAG_VOL)) { | |
| swoc::TextView text; | |
| auto n = swoc::svtoi(value, &text); | |
| if (text == value && 0 < n && n < 256) { | |
| } else { | |
| zret.note("Invalid volume index '{}'", value); | |
| } | |
| } | |
| } | |
| } | |
| zret = this->loadSpan(swoc::file::path(localpath)); | |
| } | |
| } | |
| } else { | |
| zret = Errata(make_errno_code(EBADF), "Unable to load {}", path); | |
| } | |
| return zret; | |
| } | |
| Errata | |
| Cache::loadURLs(swoc::file::path const &path) | |
| { | |
| static const swoc::TextView TAG_VOL("url"); | |
| ts::URLparser loadURLparser; | |
| Errata zret; | |
| std::error_code ec; | |
| std::string load_content = swoc::file::load(path, ec); | |
| if (ec.value() == 0) { | |
| swoc::TextView content(load_content); | |
| while (!content.empty()) { | |
| swoc::TextView blob = content.take_prefix_at('\n'); | |
| swoc::TextView tag(blob.take_prefix_at('=')); | |
| if (tag.empty()) { | |
| } else if (0 == strcasecmp(tag, TAG_VOL)) { | |
| std::string url; | |
| url.assign(blob.data(), blob.size()); | |
| int port_ptr = -1, port_len = -1; | |
| int port = loadURLparser.getPort(url, port_ptr, port_len); | |
| if (port_ptr >= 0 && port_len > 0) { | |
| url.erase(port_ptr, port_len + 1); // get rid of :PORT | |
| } | |
| std::cout << "port # " << port << ":" << port_ptr << ":" << port_len << ":" << url << std::endl; | |
| ts::CacheURL *curl = new ts::CacheURL(url, port); | |
| URLset.emplace(curl); | |
| } | |
| } | |
| } else { | |
| zret = Errata(make_errno_code(EBADF), "Unable to load ", path.string()); | |
| } | |
| return zret; | |
| } | |
| void | |
| Cache::dumpSpans(SpanDumpDepth depth) | |
| { | |
| if (depth >= SpanDumpDepth::SPAN) { | |
| for (auto &span : _spans) { | |
| if (nullptr == span->_header) { | |
| std::cout << "Span: " << span->_path.string() << " is uninitialized" << std::endl; | |
| } else { | |
| std::cout << "\n----------------------------------\n" | |
| << "Span: " << span->_path.string() << "\n----------------------------------\n" | |
| << "#Magic: " << span->_header->magic << " #Volumes: " << span->_header->num_volumes | |
| << " #in use: " << span->_header->num_used << " #free: " << span->_header->num_free | |
| << " #stripes: " << span->_header->num_diskvol_blks << " Len(bytes): " << span->_header->num_blocks.value() | |
| << std::endl; | |
| for (auto stripe : span->_stripes) { | |
| std::cout << "\n>>>>>>>>> Stripe " << static_cast<int>(stripe->_idx) << " @ " << stripe->_start | |
| << " len=" << stripe->_len.count() << " blocks " << " vol=" << static_cast<int>(stripe->_vol_idx) | |
| << " type=" << static_cast<int>(stripe->_type) << " " << (stripe->isFree() ? "free" : "in-use") << std::endl; | |
| std::cout << " " << stripe->_segments << " segments with " << stripe->_buckets << " buckets per segment for " | |
| << stripe->_buckets * stripe->_segments * ts::ENTRIES_PER_BUCKET << " total directory entries taking " | |
| << stripe->_buckets * stripe->_segments * sizeof(CacheDirEntry) * ts::ENTRIES_PER_BUCKET | |
| // << " out of " << (delta-header_len).units() << " bytes." | |
| << std::endl; | |
| if (depth >= SpanDumpDepth::STRIPE) { | |
| Errata r = stripe->loadMeta(); | |
| if (r) { | |
| // print Meta[A][HEAD] | |
| std::string MetaCopy[2] = {"A", "B"}; | |
| std::string MetaType[2] = {"HEAD", "FOOT"}; | |
| for (int i = 0; i < 2; i++) { | |
| for (int j = 0; j < 2; j++) { | |
| std::cout << "\n" << MetaCopy[i] << ":" << MetaType[j] << "\n" << std::endl; | |
| std::cout << " Magic:" << stripe->_meta[i][j].magic << "\n version: major: " << stripe->_meta[i][j].version._major | |
| << "\n version: minor: " << stripe->_meta[i][j].version._minor | |
| << "\n create_time: " << stripe->_meta[i][j].create_time | |
| << "\n write_pos: " << stripe->_meta[i][j].write_pos | |
| << "\n last_write_pos: " << stripe->_meta[i][j].last_write_pos | |
| << "\n agg_pos: " << stripe->_meta[i][j].agg_pos << "\n generation: " << stripe->_meta[i][j].generation | |
| << "\n phase: " << stripe->_meta[i][j].phase << "\n cycle: " << stripe->_meta[i][j].cycle | |
| << "\n sync_serial: " << stripe->_meta[i][j].sync_serial | |
| << "\n write_serial: " << stripe->_meta[i][j].write_serial << "\n dirty: " << stripe->_meta[i][j].dirty | |
| << "\n sector_size: " << stripe->_meta[i][j].sector_size << std::endl; | |
| } | |
| } | |
| if (!stripe->validate_sync_serial()) { | |
| std::cout << "WARNING:::::Validity check failed for sync_serials" << std::endl; | |
| } | |
| stripe->_directory.clear(); | |
| } else { | |
| std::cout << r; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void | |
| Cache::dumpVolumes() | |
| { | |
| for (auto const &elt : _volumes) { | |
| size_t size = 0; | |
| for (auto const &r : elt.second._stripes) { | |
| size += r->_len; | |
| } | |
| std::cout << "Volume " << elt.first << " has " << elt.second._stripes.size() << " stripes and " << size << " bytes" | |
| << std::endl; | |
| } | |
| } | |
| ts::CacheStripeBlocks | |
| Cache::calcTotalSpanConfiguredSize() | |
| { | |
| ts::CacheStripeBlocks zret(0); | |
| for (auto &span : _spans) { | |
| zret += round_down(span->_len); | |
| } | |
| return zret; | |
| } | |
| #if 0 | |
| ts::CacheStripeBlocks | |
| Cache::calcTotalSpanPhysicalSize() | |
| { | |
| ts::CacheStripeBlocks zret(0); | |
| for (auto span : _spans) { | |
| // This is broken, physical_size doesn't work for devices, need to fix that. | |
| zret += round_down(span->_path.physical_size()); | |
| } | |
| return zret; | |
| } | |
| #endif | |
| Cache::~Cache() | |
| { | |
| // The URL set is owned solely by this instance; the stripe hash table owns itself. | |
| for (auto *url : URLset) { | |
| delete url; | |
| } | |
| } | |
| Errata | |
| Span::load() | |
| { | |
| Errata zret; | |
| std::error_code ec; | |
| auto fs = swoc::file::status(_path, ec); | |
| if (!swoc::file::is_readable(_path)) { | |
| return Errata(make_errno_code(EPERM), R"("{}" is not readable.)", _path); | |
| } | |
| if (swoc::file::is_char_device(fs) || swoc::file::is_block_device(fs)) { | |
| return this->loadDevice(); | |
| } | |
| if (swoc::file::is_dir(fs)) { | |
| return Errata("Directory support not yet available"); | |
| } | |
| return Errata(make_errno_code(EBADF), R"("{}" is not a valid file type)", _path); | |
| } | |
| Errata | |
| Span::loadDevice() | |
| { | |
| Errata zret; | |
| int flags; | |
| flags = OPEN_RW_FLAG | |
| #if defined(O_DIRECT) | |
| | O_DIRECT | |
| #endif | |
| #if defined(O_DSYNC) | |
| | O_DSYNC | |
| #endif | |
| ; | |
| ats_scoped_fd fd(!_path.empty() ? ::open(_path.c_str(), flags) : ats_scoped_fd()); | |
| if (fd.isValid()) { | |
| if (ink_file_get_geometry(fd, _geometry)) { | |
| off_t offset = ts::CacheSpan::OFFSET; | |
| CacheStoreBlocks span_hdr_size(1); // default. | |
| static const ssize_t BUFF_SIZE = CacheStoreBlocks::SCALE; // match default span_hdr_size | |
| alignas(512) char buff[BUFF_SIZE]; | |
| ssize_t n = pread(fd, buff, BUFF_SIZE, offset); | |
| if (n >= BUFF_SIZE) { | |
| ts::SpanHeader &span_hdr = reinterpret_cast<ts::SpanHeader &>(buff); | |
| _base = round_up(offset); | |
| // See if it looks valid | |
| if (span_hdr.magic == ts::SpanHeader::MAGIC && span_hdr.num_diskvol_blks == span_hdr.num_used + span_hdr.num_free) { | |
| int nspb = span_hdr.num_diskvol_blks; | |
| span_hdr_size = round_up(sizeof(ts::SpanHeader) + (nspb - 1) * sizeof(ts::CacheStripeDescriptor)); | |
| _header.reset(new (malloc(span_hdr_size)) ts::SpanHeader); | |
| if (span_hdr_size <= BUFF_SIZE) { | |
| memcpy(static_cast<void *>(_header.get()), buff, span_hdr_size); | |
| } else { | |
| // TODO - check the pread return | |
| ssize_t n = pread(fd, _header.get(), span_hdr_size, offset); | |
| if (n < span_hdr_size) { | |
| std::cout << "Failed to read the Span Header" << std::endl; | |
| } | |
| } | |
| _len = _header->num_blocks; | |
| } else { | |
| zret.note("{} header is uninitialized or invalid", _path); | |
| std::cout << "Span: " << _path.string() << " header is uninitialized or invalid" << std::endl; | |
| _len = round_down(_geometry.totalsz) - _base; | |
| } | |
| // valid FD means the device is accessible and has enough storage to be configured. | |
| _fd = fd.release(); | |
| _offset = _base + span_hdr_size; | |
| } else { | |
| zret = Errata(make_errno_code(), "Failed to read from {}", _path); | |
| } | |
| } else { | |
| zret = Errata("Unable to get device geometry for {}", _path); | |
| } | |
| } else { | |
| zret = Errata(make_errno_code(), "Unable to open {}", _path); | |
| } | |
| return zret; | |
| } | |
| swoc::Rv<StripeSM *> | |
| Span::allocStripe(int vol_idx, const CacheStripeBlocks &len) | |
| { | |
| for (auto spot = _stripes.begin(), limit = _stripes.end(); spot != limit; ++spot) { | |
| StripeSM *stripe = *spot; | |
| if (stripe->isFree()) { | |
| if (len < stripe->_len) { | |
| // If the remains would be less than a stripe block, just take it all. | |
| if (stripe->_len <= (len + CacheStripeBlocks(1))) { | |
| stripe->_vol_idx = vol_idx; | |
| stripe->_type = 1; | |
| return stripe; | |
| } else { | |
| StripeSM *ns = new StripeSM(this, stripe->_start, len); | |
| stripe->_start += len; | |
| stripe->_len -= len; | |
| ns->_vol_idx = vol_idx; | |
| ns->_type = 1; | |
| _stripes.insert(spot, ns); | |
| return ns; | |
| } | |
| } | |
| } | |
| } | |
| return Errata("Failed to allocate stripe of size {} - no free block large enough", len); | |
| } | |
| bool | |
| Span::isEmpty() const | |
| { | |
| return std::all_of(_stripes.begin(), _stripes.end(), [](StripeSM *s) { return s->_vol_idx == 0; }); | |
| } | |
| Errata | |
| Span::clear() | |
| { | |
| StripeSM *stripe; | |
| std::for_each(_stripes.begin(), _stripes.end(), [](StripeSM *s) { delete s; }); | |
| _stripes.clear(); | |
| // Gah, due to lack of anything better, TS depends on the number of usable blocks to be consistent | |
| // with internal calculations so have to match that here. Yay. | |
| CacheStoreBlocks eff = _len - _base; // starting # of usable blocks. | |
| // The maximum number of volumes that can store stored, accounting for the space used to store the descriptors. | |
| int n = (eff - sizeof(ts::SpanHeader)) / (CacheStripeBlocks::SCALE + sizeof(CacheStripeDescriptor)); | |
| _offset = _base + round_up(sizeof(ts::SpanHeader) + (n - 1) * sizeof(CacheStripeDescriptor)); | |
| stripe = new StripeSM(this, _offset, _len - _offset); | |
| stripe->vol_init_data(); | |
| stripe->InitializeMeta(); | |
| _stripes.push_back(stripe); | |
| _free_space = stripe->_len; | |
| return Errata(); | |
| } | |
| Errata | |
| Span::updateHeader() | |
| { | |
| Errata zret; | |
| int n = _stripes.size(); | |
| CacheStripeDescriptor *sd; | |
| CacheStoreBlocks hdr_size = round_up(sizeof(ts::SpanHeader) + (n - 1) * sizeof(ts::CacheStripeDescriptor)); | |
| void *raw = ats_memalign(512, hdr_size); | |
| ts::SpanHeader *hdr = static_cast<ts::SpanHeader *>(raw); | |
| std::bitset<ts::MAX_VOLUME_IDX + 1> volume_mask; | |
| hdr->magic = ts::SpanHeader::MAGIC; | |
| hdr->num_free = 0; | |
| hdr->num_used = 0; | |
| hdr->num_diskvol_blks = n; | |
| hdr->num_blocks = _len; | |
| sd = hdr->stripes; | |
| for (auto stripe : _stripes) { | |
| sd->offset = stripe->_start; | |
| sd->len = stripe->_len; | |
| sd->vol_idx = stripe->_vol_idx; | |
| sd->type = stripe->_type; | |
| volume_mask[sd->vol_idx] = true; | |
| if (sd->vol_idx == 0) { | |
| sd->free = true; | |
| ++(hdr->num_free); | |
| } else { | |
| sd->free = false; | |
| ++(hdr->num_used); | |
| } | |
| ++sd; | |
| } | |
| volume_mask[0] = false; // don't include free stripes in distinct volume count. | |
| hdr->num_volumes = volume_mask.count(); | |
| _header.reset(hdr); | |
| if (OPEN_RW_FLAG) { | |
| ssize_t r = pwrite(_fd, hdr, hdr_size, ts::CacheSpan::OFFSET); | |
| if (r < ts::CacheSpan::OFFSET) { | |
| zret = Errata(make_errno_code(errno), "Failed to update span."); | |
| } | |
| } else { | |
| std::cout << "Writing not enabled, no updates performed" << std::endl; | |
| } | |
| return zret; | |
| } | |
| void | |
| Span::clearPermanently() | |
| { | |
| if (OPEN_RW_FLAG) { | |
| alignas(512) static char zero[CacheStoreBlocks::SCALE]; // should be all zero, it's static. | |
| std::cout << "Clearing " << _path.string() << " permanently on disk "; | |
| ssize_t n = pwrite(_fd, zero, sizeof(zero), ts::CacheSpan::OFFSET); | |
| if (n == sizeof(zero)) { | |
| std::cout << "done"; | |
| } else { | |
| const char *text = strerror(errno); | |
| std::cout << "failed"; | |
| if (n >= 0) { | |
| std::cout << " - " << n << " of " << sizeof(zero) << " bytes written"; | |
| } | |
| std::cout << " - " << text; | |
| } | |
| std::cout << std::endl; | |
| // clear the stripes as well | |
| for (auto *strp : _stripes) { | |
| strp->loadMeta(); | |
| std::cout << "Clearing stripe @" << strp->_start << " of length: " << strp->_len << std::endl; | |
| strp->clear(); | |
| } | |
| } else { | |
| std::cout << "Clearing " << _path.string() << " not performed, write not enabled" << std::endl; | |
| } | |
| } | |
| // explicit pair for random table in build_vol_hash_table | |
| struct rtable_pair { | |
| unsigned int rval; ///< relative value, used to sort. | |
| unsigned int idx; ///< volume mapping table index. | |
| }; | |
| // comparison operator for random table in build_vol_hash_table | |
| // sorts based on the randomly assigned rval | |
| static int | |
| cmprtable(const void *aa, const void *bb) | |
| { | |
| rtable_pair *a = (rtable_pair *)aa; | |
| rtable_pair *b = (rtable_pair *)bb; | |
| if (a->rval < b->rval) { | |
| return -1; | |
| } | |
| if (a->rval > b->rval) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| unsigned int | |
| next_rand(unsigned int *p) | |
| { | |
| unsigned int seed = *p; | |
| seed = 1103515145 * seed + 12345; | |
| *p = seed; | |
| return seed; | |
| } | |
| void | |
| Cache::build_stripe_hash_table() | |
| { | |
| int num_stripes = globalVec_stripe.size(); | |
| CacheStoreBlocks total; | |
| unsigned int *forvol = static_cast<unsigned int *>(ats_malloc(sizeof(unsigned int) * num_stripes)); | |
| unsigned int *gotvol = static_cast<unsigned int *>(ats_malloc(sizeof(unsigned int) * num_stripes)); | |
| unsigned int *rnd = static_cast<unsigned int *>(ats_malloc(sizeof(unsigned int) * num_stripes)); | |
| unsigned short *ttable = static_cast<unsigned short *>(ats_malloc(sizeof(unsigned short) * STRIPE_HASH_TABLE_SIZE)); | |
| unsigned int *rtable_entries = static_cast<unsigned int *>(ats_malloc(sizeof(unsigned int) * num_stripes)); | |
| unsigned int rtable_size = 0; | |
| int i = 0; | |
| uint64_t used = 0; | |
| // estimate allocation | |
| for (auto &elt : globalVec_stripe) { | |
| // printf("stripe length %" PRId64 "\n", elt->_len.count()); | |
| rtable_entries[i] = static_cast<int64_t>(elt->_len) / Vol_hash_alloc_size; | |
| rtable_size += rtable_entries[i]; | |
| uint64_t x = elt->hash_id.fold(); | |
| // seed random number generator | |
| rnd[i] = static_cast<unsigned int>(x); | |
| total += elt->_len; | |
| i++; | |
| } | |
| i = 0; | |
| for (auto &elt : globalVec_stripe) { | |
| forvol[i] = total ? static_cast<int64_t>(STRIPE_HASH_TABLE_SIZE * elt->_len) / total : 0; | |
| used += forvol[i]; | |
| gotvol[i] = 0; | |
| i++; | |
| } | |
| // spread around the excess | |
| int extra = STRIPE_HASH_TABLE_SIZE - used; | |
| for (int i = 0; i < extra; i++) { | |
| forvol[i % num_stripes]++; | |
| } | |
| // initialize table to "empty" | |
| for (int i = 0; i < STRIPE_HASH_TABLE_SIZE; i++) { | |
| ttable[i] = STRIPE_HASH_EMPTY; | |
| } | |
| // generate random numbers proportional to allocation | |
| rtable_pair *rtable = static_cast<rtable_pair *>(ats_malloc(sizeof(rtable_pair) * rtable_size)); | |
| int rindex = 0; | |
| for (int i = 0; i < num_stripes; i++) { | |
| for (int j = 0; j < static_cast<int>(rtable_entries[i]); j++) { | |
| rtable[rindex].rval = next_rand(&rnd[i]); | |
| rtable[rindex].idx = i; | |
| rindex++; | |
| } | |
| } | |
| assert(rindex == (int)rtable_size); | |
| // sort (rand #, vol $ pairs) | |
| qsort(rtable, rtable_size, sizeof(rtable_pair), cmprtable); | |
| unsigned int width = (1LL << 32) / STRIPE_HASH_TABLE_SIZE; | |
| unsigned int pos; // target position to allocate | |
| // select vol with closest random number for each bucket | |
| i = 0; // index moving through the random numbers | |
| for (int j = 0; j < STRIPE_HASH_TABLE_SIZE; j++) { | |
| pos = width / 2 + j * width; // position to select closest to | |
| while (pos > rtable[i].rval && i < static_cast<int>(rtable_size) - 1) { | |
| i++; | |
| } | |
| ttable[j] = rtable[i].idx; | |
| gotvol[rtable[i].idx]++; | |
| } | |
| for (int i = 0; i < num_stripes; i++) { | |
| printf("build_vol_hash_table index %d mapped to %d requested %d got %d\n", i, i, forvol[i], gotvol[i]); | |
| } | |
| // Assigning releases any table a previous call installed. | |
| stripes_hash_table = ttable; | |
| ats_free(forvol); | |
| ats_free(gotvol); | |
| ats_free(rnd); | |
| ats_free(rtable_entries); | |
| ats_free(rtable); | |
| } | |
| StripeSM * | |
| Cache::key_to_stripe(CryptoHash *key, [[maybe_unused]] const char *hostname, [[maybe_unused]] int host_len) | |
| { | |
| uint32_t h = (key->slice32(2) >> DIR_TAG_WIDTH) % STRIPE_HASH_TABLE_SIZE; | |
| return globalVec_stripe[stripes_hash_table[h]]; | |
| } | |
| /* --------------------------------------------------------------------------------------- */ | |
| Errata | |
| VolumeConfig::load(swoc::file::path const &path) | |
| { | |
| static const swoc::TextView TAG_SIZE("size"); | |
| static const swoc::TextView TAG_VOL("volume"); | |
| Errata zret; | |
| int ln = 0; | |
| std::error_code ec; | |
| std::string load_content = swoc::file::load(path, ec); | |
| if (ec.value() == 0) { | |
| swoc::TextView content(load_content); | |
| while (content) { | |
| Data v; | |
| ++ln; | |
| swoc::TextView line = content.take_prefix_at('\n'); | |
| line.ltrim_if(&isspace); | |
| if (line.empty() || '#' == *line) { | |
| continue; | |
| } | |
| while (line) { | |
| swoc::TextView value(line.take_prefix_if(&isspace)); | |
| swoc::TextView tag(value.take_prefix_at('=')); | |
| if (tag.empty()) { | |
| zret.note("Line {} is invalid", ln); | |
| } else if (0 == strcasecmp(tag, TAG_SIZE)) { | |
| if (v.hasSize()) { | |
| zret.note("Line {} has field {} more than once", ln, TAG_SIZE); | |
| } else { | |
| swoc::TextView text; | |
| auto n = swoc::svtoi(value, &text); | |
| if (text) { | |
| swoc::TextView percent(text.data_end(), value.data_end()); // clip parsed number. | |
| if (percent.empty()) { | |
| v._size = CacheStripeBlocks(round_up(Megabytes(n))); | |
| if (v._size.count() != n) { | |
| zret.note("Line {} size {} was rounded up to {}", ln, n, v._size); | |
| } | |
| } else if ('%' == *percent && percent.size() == 1) { | |
| v._percent = n; | |
| } else { | |
| zret.note("Line {} has invalid value '{}' for {} field", ln, value, TAG_SIZE); | |
| } | |
| } else { | |
| zret.note("Line {} has invalid value '{}' for {} field", ln, value, TAG_SIZE); | |
| } | |
| } | |
| } else if (0 == strcasecmp(tag, TAG_VOL)) { | |
| if (v.hasIndex()) { | |
| zret.note("Line {} has field {} more than once", ln, TAG_VOL); | |
| } else { | |
| swoc::TextView text; | |
| auto n = swoc::svtoi(value, &text); | |
| if (text == value) { | |
| v._idx = n; | |
| } else { | |
| zret.note("Line {} has invalid value '{}' for {} field", ln, value, TAG_VOL); | |
| } | |
| } | |
| } | |
| } | |
| if (v.hasSize() && v.hasIndex()) { | |
| _volumes.push_back(std::move(v)); | |
| } else { | |
| if (!v.hasSize()) { | |
| zret.note("Line {} does not have the required field {}", ln, TAG_SIZE); | |
| } | |
| if (!v.hasIndex()) { | |
| zret.note("Line {} does not have the required field {}", ln, TAG_VOL); | |
| } | |
| } | |
| } | |
| } else { | |
| zret = Errata(make_errno_code(EBADF), "Unable to load ", path); | |
| } | |
| return zret; | |
| } | |
| } // namespace ct | |
| using namespace ct; | |
| void | |
| List_Stripes(Cache::SpanDumpDepth depth) | |
| { | |
| Cache cache; | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| cache.dumpSpans(depth); | |
| cache.dumpVolumes(); | |
| } | |
| } | |
| void | |
| Cmd_Allocate_Empty_Spans() | |
| { | |
| VolumeAllocator va; | |
| // OPEN_RW_FLAG = O_RDWR; | |
| if ((err = va.load(SpanFile, VolumeFile))) { | |
| va.fillEmptySpans(); | |
| } | |
| } | |
| void | |
| Simulate_Span_Allocation() | |
| { | |
| VolumeAllocator va; | |
| if (VolumeFile.empty()) { | |
| err.note("Volume config file not set"); | |
| } | |
| if (SpanFile.empty()) { | |
| err.note("Span file not set"); | |
| } | |
| if (err) { | |
| if ((err = va.load(SpanFile, VolumeFile)).is_ok()) { | |
| err = va.fillAllSpans(); | |
| va.dumpVolumes(); | |
| } | |
| } | |
| } | |
| void | |
| Clear_Spans() | |
| { | |
| Cache cache; | |
| if (!OPEN_RW_FLAG) { | |
| err.note("Writing Not Enabled.. Please use --write to enable writing to disk"); | |
| return; | |
| } | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| for (auto &span : cache._spans) { | |
| span->clearPermanently(); | |
| } | |
| } | |
| } | |
| void | |
| Find_Stripe(swoc::file::path const &input_file_path) | |
| { | |
| // scheme=http user=u password=p host=172.28.56.109 path=somepath query=somequery port=1234 | |
| // input file format: scheme://hostname:port/somepath;params?somequery user=USER password=PASS | |
| // user, password, are optional; scheme and host are required | |
| // char* h= http://user:pass@IPADDRESS/path_to_file;?port <== this is the format we need | |
| // url_matcher matcher; | |
| // if (matcher.match(h)) | |
| // std::cout << h << " : is valid" << std::endl; | |
| // else | |
| // std::cout << h << " : is NOT valid" << std::endl; | |
| Cache cache; | |
| if (!input_file_path.empty()) { | |
| printf("passed argv %s\n", input_file_path.c_str()); | |
| } | |
| cache.loadURLs(input_file_path); | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| cache.dumpSpans(Cache::SpanDumpDepth::SPAN); | |
| cache.build_stripe_hash_table(); | |
| for (auto host : cache.URLset) { | |
| CryptoContext ctx; | |
| CryptoHash hashT; | |
| swoc::LocalBufferWriter<33> w; | |
| ctx.update(host->url.data(), host->url.size()); | |
| ctx.update(&host->port, sizeof(host->port)); | |
| ctx.finalize(hashT); | |
| StripeSM *stripe_ = cache.key_to_stripe(&hashT, host->url.data(), host->url.size()); | |
| w.print("{}", hashT); | |
| printf("hash of %.*s is %.*s: Stripe %s \n", static_cast<int>(host->url.size()), host->url.data(), | |
| static_cast<int>(w.size()), w.data(), stripe_->hashText.data()); | |
| } | |
| } | |
| } | |
| void | |
| dir_check() | |
| { | |
| Cache cache; | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| cache.dumpSpans(Cache::SpanDumpDepth::SPAN); | |
| for (auto &stripe : cache.globalVec_stripe) { | |
| stripe->dir_check(); | |
| } | |
| } | |
| printf("\nCHECK succeeded\n"); | |
| } | |
| void | |
| walk_bucket_chain(const std::string &devicePath) | |
| { | |
| Cache cache; | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| cache.dumpSpans(Cache::SpanDumpDepth::SPAN); | |
| for (auto &sp : cache._spans) { | |
| if (devicePath.size() > 0 && sp->_path.view() == devicePath) { | |
| for (auto strp : sp->_stripes) { | |
| strp->loadMeta(); | |
| strp->loadDir(); | |
| strp->walk_all_buckets(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void | |
| Clear_Span(const std::string &devicePath) | |
| { | |
| Cache cache; | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| cache.dumpSpans(Cache::SpanDumpDepth::SPAN); | |
| for (auto &sp : cache._spans) { | |
| if (devicePath.size() > 0 && sp->_path.view() == devicePath) { | |
| printf("clearing %s\n", devicePath.data()); | |
| sp->clearPermanently(); | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| void | |
| Check_Freelist(const std::string &devicePath) | |
| { | |
| Cache cache; | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| cache.dumpSpans(Cache::SpanDumpDepth::SPAN); | |
| for (auto &sp : cache._spans) { | |
| if (devicePath.size() > 0 && sp->_path.view() == devicePath) { | |
| printf("Scanning %s\n", devicePath.data()); | |
| for (auto strp : sp->_stripes) { | |
| strp->loadMeta(); | |
| strp->loadDir(); | |
| for (int s = 0; s < strp->_segments; s++) { | |
| strp->check_loop(s); | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| void | |
| Init_disk(swoc::file::path const &input_file_path) | |
| { | |
| Cache cache; | |
| VolumeAllocator va; | |
| if (!OPEN_RW_FLAG) { | |
| err.note("Writing Not Enabled.. Please use --write to enable writing to disk"); | |
| return; | |
| } | |
| err = va.load(SpanFile, VolumeFile); | |
| va.allocateSpan(input_file_path); | |
| } | |
| void | |
| Get_Response(swoc::file::path const &input_file_path) | |
| { | |
| // scheme=http user=u password=p host=172.28.56.109 path=somepath query=somequery port=1234 | |
| // input file format: scheme://hostname:port/somepath;params?somequery user=USER password=PASS | |
| // user, password, are optional; scheme and host are required | |
| // char* h= http://user:pass@IPADDRESS/path_to_file;?port <== this is the format we need | |
| Cache cache; | |
| if (!input_file_path.empty()) { | |
| printf("passed argv %s\n", input_file_path.c_str()); | |
| } | |
| cache.loadURLs(input_file_path); | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| cache.dumpSpans(Cache::SpanDumpDepth::SPAN); | |
| cache.build_stripe_hash_table(); | |
| for (auto host : cache.URLset) { | |
| CryptoContext ctx; | |
| CryptoHash hashT; | |
| swoc::LocalBufferWriter<33> w; | |
| ctx.update(host->url.data(), host->url.size()); | |
| ctx.update(&host->port, sizeof(host->port)); | |
| ctx.finalize(hashT); | |
| StripeSM *stripe_ = cache.key_to_stripe(&hashT, host->url.data(), host->url.size()); | |
| w.print("{}", hashT); | |
| printf("hash of %.*s is %.*s: Stripe %s \n", static_cast<int>(host->url.size()), host->url.data(), | |
| static_cast<int>(w.size()), w.data(), stripe_->hashText.data()); | |
| CacheDirEntry *dir_result = nullptr; | |
| stripe_->loadMeta(); | |
| stripe_->loadDir(); | |
| stripe_->dir_probe(&hashT, dir_result, nullptr); | |
| } | |
| } | |
| } | |
| void static scan_span(Span &span, swoc::file::path const ®ex_path) | |
| { | |
| for (auto strp : span._stripes) { | |
| strp->loadMeta(); | |
| strp->loadDir(); | |
| if (!regex_path.empty()) { | |
| CacheScan cs(strp, regex_path); | |
| cs.Scan(true); | |
| } else { | |
| CacheScan cs(strp); | |
| cs.Scan(false); | |
| } | |
| } | |
| } | |
| void | |
| Scan_Cache(swoc::file::path const ®ex_path) | |
| { | |
| Cache cache; | |
| std::vector<std::thread> threadPool; | |
| if ((err = cache.loadSpan(SpanFile))) { | |
| if (err.length()) { | |
| return; | |
| } | |
| cache.dumpSpans(Cache::SpanDumpDepth::SPAN); | |
| for (auto &sp : cache._spans) { | |
| threadPool.emplace_back(scan_span, std::ref(*sp), regex_path); | |
| } | |
| for (auto &th : threadPool) { | |
| th.join(); | |
| } | |
| } | |
| } | |
| int | |
| main([[maybe_unused]] int argc, const char *argv[]) | |
| { | |
| swoc::file::path input_url_file; | |
| std::string inputFile; | |
| parser.add_global_usage(std::string(argv[0]) + " --spans <SPAN> --volume <FILE> <COMMAND> [<SUBCOMMAND> ...]\n"); | |
| parser.require_commands() | |
| .add_option("--help", "-h", "") | |
| .add_option("--spans", "-s", "", "", 1) | |
| .add_option("--volumes", "-v", "", "", 1) | |
| .add_option("--write", "-w", "") | |
| .add_option("--input", "-i", "", "", 1) | |
| .add_option("--device", "-d", "", "", 1) | |
| .add_option("--aos", "-o", "", "", 1); | |
| parser.add_command("list", "List elements of the cache", []() { List_Stripes(Cache::SpanDumpDepth::SPAN); }) | |
| .add_command("stripes", "List the stripes", []() { List_Stripes(Cache::SpanDumpDepth::STRIPE); }); | |
| parser.add_command("clear", "Clear spans", []() { Clear_Spans(); }).add_command("span", "clear an specific span", [&]() { | |
| Clear_Span(inputFile); | |
| }); | |
| auto &c = parser.add_command("dir_check", "cache check").require_commands(); | |
| c.add_command("full", "Full report of the cache storage", &dir_check); | |
| c.add_command("freelist", "check the freelist for loop", [&]() { Check_Freelist(inputFile); }); | |
| c.add_command("bucket_chain", "walk bucket chains for loops", [&]() { walk_bucket_chain(inputFile); }); | |
| parser.add_command("volumes", "Volumes", &Simulate_Span_Allocation); | |
| parser.add_command("alloc", "Storage allocation") | |
| .require_commands() | |
| .add_command("free", "Allocate storage on free (empty) spans", &Cmd_Allocate_Empty_Spans); | |
| parser.add_command("find", "Find Stripe Assignment", [&]() { Find_Stripe(input_url_file); }); | |
| parser.add_command("clearspan", "clear specific span").add_command("span", "device path", [&]() { Clear_Span(inputFile); }); | |
| parser.add_command("retrieve", " retrieve the response of the given list of URLs", [&]() { Get_Response(input_url_file); }); | |
| parser.add_command("init", " Initializes uninitialized span", [&]() { Init_disk(input_url_file); }); | |
| parser.add_command("scan", " Scans the whole cache and lists the urls of the cached contents", | |
| [&]() { Scan_Cache(input_url_file); }); | |
| // parse the arguments | |
| auto arguments = parser.parse(argv); | |
| if (auto data = arguments.get("spans")) { | |
| SpanFile = data.value(); | |
| } | |
| if (auto data = arguments.get("volumes")) { | |
| VolumeFile = data.value(); | |
| } | |
| if (auto data = arguments.get("input")) { | |
| input_url_file = data.value(); | |
| } | |
| if (auto data = arguments.get("aos")) { | |
| cache_config_min_average_object_size = std::stoi(data.value()); | |
| } | |
| if (auto data = arguments.get("device")) { | |
| inputFile = data.value(); | |
| } | |
| if (auto data = arguments.get("write")) { | |
| OPEN_RW_FLAG = O_RDWR; | |
| std::cout << "NOTE: Writing to physical devices enabled" << std::endl; | |
| } | |
| if (arguments.has_action()) { | |
| arguments.invoke(); | |
| } | |
| if (err.length()) { | |
| std::cerr << err; | |
| exit(1); | |
| } | |
| return 0; | |
| } |