| /* |
| 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 <unistd.h> |
| |
| #include "lru_policy.h" |
| |
| #define MINIMUM_BUCKET_SIZE 10 |
| static LRUEntry NULL_LRU_ENTRY; // Used to create an "empty" new LRUEntry |
| |
| LRUPolicy::~LRUPolicy() |
| { |
| TSDebug(PLUGIN_NAME, "LRUPolicy DTOR"); |
| TSMutexLock(_lock); |
| |
| _map.clear(); |
| _list.clear(); |
| _list_size = 0; |
| _freelist.clear(); |
| _freelist_size = 0; |
| |
| TSMutexUnlock(_lock); |
| TSMutexDestroy(_lock); |
| } |
| |
| bool |
| LRUPolicy::parseOption(int opt, char *optarg) |
| { |
| switch (opt) { |
| case 'b': |
| _buckets = static_cast<unsigned>(strtol(optarg, nullptr, 10)); |
| if (_buckets < MINIMUM_BUCKET_SIZE) { |
| TSError("%s: Enforcing minimum LRU bucket size of %d", PLUGIN_NAME, MINIMUM_BUCKET_SIZE); |
| TSDebug(PLUGIN_NAME, "enforcing minimum bucket size of %d", MINIMUM_BUCKET_SIZE); |
| _buckets = MINIMUM_BUCKET_SIZE; |
| } |
| break; |
| case 'h': |
| _hits = static_cast<unsigned>(strtol(optarg, nullptr, 10)); |
| break; |
| case 'l': |
| _label = optarg; |
| break; |
| default: |
| // All other options are unsupported for this policy |
| return false; |
| } |
| |
| // This doesn't have to be perfect, since this is just chance sampling. |
| // coverity[dont_call] |
| srand48(static_cast<long>(time(nullptr)) ^ static_cast<long>(getpid()) ^ static_cast<long>(getppid())); |
| |
| return true; |
| } |
| |
| bool |
| LRUPolicy::doPromote(TSHttpTxn txnp) |
| { |
| LRUHash hash; |
| LRUMap::iterator map_it; |
| char *url = nullptr; |
| int url_len = 0; |
| bool ret = false; |
| TSMBuffer request; |
| TSMLoc req_hdr; |
| |
| if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &request, &req_hdr)) { |
| TSMLoc c_url = TS_NULL_MLOC; |
| |
| // Get the cache key URL (for now), since this has better lookup behavior when using |
| // e.g. the cachekey plugin. |
| if (TS_SUCCESS == TSUrlCreate(request, &c_url)) { |
| if (TS_SUCCESS == TSHttpTxnCacheLookupUrlGet(txnp, request, c_url)) { |
| url = TSUrlStringGet(request, c_url, &url_len); |
| TSHandleMLocRelease(request, TS_NULL_MLOC, c_url); |
| } |
| } |
| TSHandleMLocRelease(request, TS_NULL_MLOC, req_hdr); |
| } |
| |
| // Generally shouldn't happen ... |
| if (!url) { |
| return false; |
| } |
| |
| TSDebug(PLUGIN_NAME, "LRUPolicy::doPromote(%.*s%s)", url_len > 100 ? 100 : url_len, url, url_len > 100 ? "..." : ""); |
| hash.init(url, url_len); |
| TSfree(url); |
| |
| // We have to hold the lock across all list and hash access / updates |
| TSMutexLock(_lock); |
| |
| map_it = _map.find(&hash); |
| if (_map.end() != map_it) { |
| // We have an entry in the LRU |
| TSAssert(_list_size > 0); // mismatch in the LRUs hash and list, shouldn't happen |
| incrementStat(lru_hit_id, 1); |
| if (++(map_it->second->second) >= _hits) { |
| // Promoted! Cleanup the LRU, and signal success. Save the promoted entry on the freelist. |
| TSDebug(PLUGIN_NAME, "saving the LRUEntry to the freelist"); |
| _freelist.splice(_freelist.begin(), _list, map_it->second); |
| ++_freelist_size; |
| --_list_size; |
| _map.erase(map_it->first); |
| incrementStat(promoted_id, 1); |
| incrementStat(freelist_size_id, 1); |
| decrementStat(lru_size_id, 1); |
| ret = true; |
| } else { |
| // It's still not promoted, make sure it's moved to the front of the list |
| TSDebug(PLUGIN_NAME, "still not promoted, got %d hits so far", map_it->second->second); |
| _list.splice(_list.begin(), _list, map_it->second); |
| } |
| } else { |
| // New LRU entry for the URL, try to repurpose the list entry as much as possible |
| incrementStat(lru_miss_id, 1); |
| if (_list_size >= _buckets) { |
| TSDebug(PLUGIN_NAME, "repurposing last LRUHash entry"); |
| _list.splice(_list.begin(), _list, --_list.end()); |
| _map.erase(&(_list.begin()->first)); |
| incrementStat(lru_vacated_id, 1); |
| } else if (_freelist_size > 0) { |
| TSDebug(PLUGIN_NAME, "reusing LRUEntry from freelist"); |
| _list.splice(_list.begin(), _freelist, _freelist.begin()); |
| --_freelist_size; |
| ++_list_size; |
| incrementStat(lru_size_id, 1); |
| decrementStat(freelist_size_id, 1); |
| } else { |
| TSDebug(PLUGIN_NAME, "creating new LRUEntry"); |
| _list.push_front(NULL_LRU_ENTRY); |
| ++_list_size; |
| incrementStat(lru_size_id, 1); |
| } |
| // Update the "new" LRUEntry and add it to the hash |
| _list.begin()->first = hash; |
| _list.begin()->second = 1; |
| _map[&(_list.begin()->first)] = _list.begin(); |
| } |
| |
| TSMutexUnlock(_lock); |
| |
| return ret; |
| } |
| |
| bool |
| LRUPolicy::stats_add(const char *remap_id) |
| |
| { |
| std::string_view remap_identifier = remap_id; |
| const std::tuple<std::string_view, int *> stats[] = { |
| {"cache_hits", &cache_hits_id}, {"freelist_size", &freelist_size_id}, |
| {"lru_size", &lru_size_id}, {"lru_hit", &lru_hit_id}, |
| {"lru_miss", &lru_miss_id}, {"lru_vacated", &lru_vacated_id}, |
| {"promoted", &promoted_id}, {"total_requests", &total_requests_id}, |
| }; |
| |
| if (nullptr == remap_id) { |
| TSError("[%s] no remap identifier specified for for stats, no stats will be used", PLUGIN_NAME); |
| return false; |
| } |
| |
| for (const auto &stat : stats) { |
| std::string_view name = std::get<0>(stat); |
| int *id = std::get<1>(stat); |
| if ((*(id) = create_stat(name, remap_identifier)) == TS_ERROR) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |