This is an automated email from the ASF dual-hosted git repository.

mochen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 4fcbbbfbe7 Conditional Slicing (#11618)
4fcbbbfbe7 is described below

commit 4fcbbbfbe7a43a621aac02010572e7a5323c55e2
Author: Mo Chen <[email protected]>
AuthorDate: Mon Aug 12 13:47:01 2024 -0500

    Conditional Slicing (#11618)
    
    Add a configuration for slice plugin to limit slicing to large objects 
only.  See documentation change for details.
---
 doc/admin-guide/plugins/slice.en.rst               |  65 +++++++
 .../cache_range_requests/cache_range_requests.cc   |  34 +++-
 plugins/slice/CMakeLists.txt                       |   1 +
 plugins/slice/Config.cc                            |  86 +++++++++-
 plugins/slice/Config.h                             |  26 ++-
 plugins/slice/ObjectSizeCache.cc                   | 116 +++++++++++++
 plugins/slice/ObjectSizeCache.h                    |  78 +++++++++
 plugins/slice/server.cc                            |  29 ++++
 plugins/slice/slice.cc                             | 187 ++++++++++++++++++---
 plugins/slice/unit-tests/CMakeLists.txt            |   7 +-
 plugins/slice/unit-tests/test_cache.cc             | 173 +++++++++++++++++++
 .../pluginTest/slice/slice_conditional.test.py     | 156 +++++++++++++++++
 12 files changed, 924 insertions(+), 34 deletions(-)

diff --git a/doc/admin-guide/plugins/slice.en.rst 
b/doc/admin-guide/plugins/slice.en.rst
index 9fa2832d8a..9215d4cc84 100644
--- a/doc/admin-guide/plugins/slice.en.rst
+++ b/doc/admin-guide/plugins/slice.en.rst
@@ -153,6 +153,41 @@ The slice plugin supports the following options::
         Enable slice plugin to strip Range header for HEAD requests.
         -h for short
 
+    --minimum-size (optional)
+    --metadata-cache-size (optional)
+    --stats-prefix (optional)
+        In combination, these three options allow for conditional slice.
+        Specify the minimum size object to slice with --minimum-size.  Allowed
+        values are the same as --blockbytes.  Conditional slicing uses a cache
+        of object sizes to make the decision of whether to slice.  The cache
+        will only store the URL of large objects as they are discovered in
+        origin responses.  You should set the --metadata-cache-size to by
+        estimating the working set size of large objects.  You can use
+        stats to determine whether --metadata-cache-size was set optimally.
+        Stat names are prefixed with the value of --stats-prefix.  The names
+        are:
+
+        <prefix>.metadata_cache.true_large_objects - large object cache hits
+        <prefix>.metadata_cache.true_small_objects - small object cache hits
+        <prefix>.metadata_cache.false_large_objects - large object cache misses
+        <prefix>.metadata_cache.false_small_objects - small object cache misses
+        <prefix>.metadata_cache.no_content_length - number of responses 
without content length
+        <prefix>.metadata_cache.bad_content_length - number of responses with 
invalid content length
+        <prefix>.metadata_cache.no_url - number of responses where URL parsing 
failed
+
+        If an object size is not found in the object size cache, the plugin
+        will not slice the object, and will turn off ATS cache on this request.
+        The object size will be cached in following requests, and slice will
+        proceed normally if the object meets the minimum size requirement.
+
+        Range requests from the client for small objects are passed through the
+        plugin unchanged.  If you use the `cache_range_requests` plugin, slice 
plugin
+        will communicate with `cache_range_requests` using an internal header
+        that causes `cache_range_requests` to be bypassed in such requests, and
+        allow ATS to handle those range requests internally.
+
+
+
 Examples::
 
     @plugin=slice.so @pparam=--blockbytes=1000000 
@plugin=cache_range_requests.so
@@ -307,6 +342,36 @@ The functionality works with `--ref-relative` both enabled 
and disabled. If `--r
 disabled (using slice 0 as the reference block), requesting to PURGE a block 
that does not have
 slice 0 in its range will still PURGE the slice 0 block, as the reference 
block is always processed.
 
+Conditional Slicing
+-------------------
+
+The goal of conditional slicing is to slice large objects and avoid the cost 
of slicing on small
+objects.  If `--minimum-size` is specified, conditional slicing is enabled and 
works as follows.
+
+The plugin builds a object size cache in memory.  The key is the URL of the 
object.  Only
+large object URLs are written to the cache.  The object size cache uses CLOCK 
eviction algorithm
+in order to have lazy promotion behavior.
+
+When a URL not found in the object size cache, the plugin treats the object as 
a small object.  It
+will not intercept the request.  The request is processed by ATS without any 
slice logic.  Upon
+receiving a response, the slice plugin will check the response content length 
to update the object
+size cache if necessary.
+
+When a large URL is requested for the first time, conditional slicing will not 
intercept that
+request since the URL is not known to be large.  This will cause an ATS cache 
miss and the request
+will go to origin server.  Slice plugin will turn off writing to cache for 
this response, because
+it expects to slice this object in future requests.
+
+If the object size cache evicts a URL, the size of the object for that URL 
will need to be learned
+again in a subsequent request, and the behavior above will happen again.
+
+If the URL is found in the object size cache, conditional slicing treats the 
object as a large object
+and will activate the slicing logic as described in the rest of this document.
+
+If the client sends a range request, and that URL is not in the object size 
cache, the slice plugin
+will forward the range request to ATS core.  It also attaches an internal 
header in order to deactivate
+the `cache_range_requests` plugin for this range request.
+
 Important Notes
 ===============
 
diff --git a/plugins/cache_range_requests/cache_range_requests.cc 
b/plugins/cache_range_requests/cache_range_requests.cc
index 4481b60ce4..37e81753f5 100644
--- a/plugins/cache_range_requests/cache_range_requests.cc
+++ b/plugins/cache_range_requests/cache_range_requests.cc
@@ -50,9 +50,10 @@ enum parent_select_mode_t {
   PS_CACHEKEY_URL, // Set parent selection url to cache_key url
 };
 
-constexpr std::string_view DefaultImsHeader = {"X-Crr-Ims"};
-constexpr std::string_view SLICE_CRR_HEADER = {"Slice-Crr-Status"};
-constexpr std::string_view SLICE_CRR_VAL    = "1";
+constexpr std::string_view DefaultImsHeader  = {"X-Crr-Ims"};
+constexpr std::string_view SLICE_CRR_HEADER  = {"Slice-Crr-Status"};
+constexpr std::string_view SLICE_CRR_VAL     = "1";
+constexpr std::string_view SKIP_CRR_HDR_NAME = {"X-Skip-Crr"};
 
 struct pluginconfig {
   parent_select_mode_t ps_mode{PS_DEFAULT};
@@ -86,6 +87,7 @@ bool                 set_header(TSMBuffer, TSMLoc, const char 
*, int, const char
 int                  transaction_handler(TSCont, TSEvent, void *);
 struct pluginconfig *create_pluginconfig(int argc, char *const argv[]);
 void                 delete_pluginconfig(pluginconfig *const);
+static bool          has_skip_crr_header(TSHttpTxn);
 
 /**
  * Creates pluginconfig data structure
@@ -192,12 +194,32 @@ handle_read_request_header(TSCont /* txn_contp ATS_UNUSED 
*/, TSEvent /* event A
 {
   TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
 
-  range_header_check(txnp, gPluginConfig);
+  if (!has_skip_crr_header(txnp)) {
+    range_header_check(txnp, gPluginConfig);
+  }
 
   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
   return 0;
 }
 
+static bool
+has_skip_crr_header(TSHttpTxn txnp)
+{
+  TSMBuffer hdr_buf = nullptr;
+  TSMLoc    hdr_loc = TS_NULL_MLOC;
+  bool      ret     = false;
+
+  if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &hdr_buf, &hdr_loc)) {
+    TSMLoc const skip_crr_loc = TSMimeHdrFieldFind(hdr_buf, hdr_loc, 
SKIP_CRR_HDR_NAME.data(), SKIP_CRR_HDR_NAME.length());
+    if (TS_NULL_MLOC != skip_crr_loc) {
+      TSHandleMLocRelease(hdr_buf, hdr_loc, skip_crr_loc);
+      ret = true;
+    }
+    TSHandleMLocRelease(hdr_buf, TS_NULL_MLOC, hdr_loc);
+  }
+  return ret;
+}
+
 /**
  * Reads the client request header and if this is a range request:
  *
@@ -682,7 +704,9 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo 
* /* rri */)
 {
   pluginconfig *const pc = static_cast<pluginconfig *>(ih);
 
-  range_header_check(txnp, pc);
+  if (!has_skip_crr_header(txnp)) {
+    range_header_check(txnp, pc);
+  }
 
   return TSREMAP_NO_REMAP;
 }
diff --git a/plugins/slice/CMakeLists.txt b/plugins/slice/CMakeLists.txt
index f63bd2efe4..e82bc46c5c 100644
--- a/plugins/slice/CMakeLists.txt
+++ b/plugins/slice/CMakeLists.txt
@@ -31,6 +31,7 @@ add_atsplugin(
   slice.cc
   transfer.cc
   util.cc
+  ObjectSizeCache.cc
 )
 
 target_link_libraries(slice PRIVATE PCRE::PCRE)
diff --git a/plugins/slice/Config.cc b/plugins/slice/Config.cc
index 3a7ef883f7..9d62dd71f8 100644
--- a/plugins/slice/Config.cc
+++ b/plugins/slice/Config.cc
@@ -18,6 +18,7 @@
 
 #include "Config.h"
 
+#include <cassert>
 #include <cctype>
 #include <cinttypes>
 #include <cstdlib>
@@ -123,13 +124,16 @@ Config::fromArgs(int const argc, char const *const argv[])
     {const_cast<char *>("blockbytes-test"),      required_argument, nullptr, 
't'},
     {const_cast<char *>("prefetch-count"),       required_argument, nullptr, 
'f'},
     {const_cast<char *>("strip-range-for-head"), no_argument,       nullptr, 
'h'},
+    {const_cast<char *>("minimum-size"),         required_argument, nullptr, 
'm'},
+    {const_cast<char *>("metadata-cache-size"),  required_argument, nullptr, 
'z'},
+    {const_cast<char *>("stats-prefix"),         required_argument, nullptr, 
'x'},
     {nullptr,                                    0,                 nullptr, 0 
 },
   };
 
   // getopt assumes args start at '1' so this hack is needed
   char *const *argvp = (const_cast<char *const *>(argv) - 1);
   for (;;) {
-    int const opt = getopt_long(argc + 1, argvp, "b:dc:e:i:lp:r:s:t:", 
longopts, nullptr);
+    int const opt = getopt_long(argc + 1, argvp, "b:dc:e:i:lm:p:r:s:t:x:z:", 
longopts, nullptr);
     if (-1 == opt) {
       break;
     }
@@ -228,6 +232,29 @@ Config::fromArgs(int const argc, char const *const argv[])
     case 'h': {
       m_head_strip_range = true;
     } break;
+    case 'm': {
+      int64_t const bytesread = bytesFrom(optarg);
+      if (bytesread < 0) {
+        DEBUG_LOG("Invalid minimum-size: %s", optarg);
+      }
+      m_min_size_to_slice = bytesread;
+      DEBUG_LOG("Only slicing objects %" PRIu64 " bytes or larger", 
m_min_size_to_slice);
+    } break;
+    case 'z': {
+      try {
+        size_t size = std::stoul(optarg);
+        setCacheSize(size);
+        DEBUG_LOG("Metadata cache size: %zu entries", size);
+      } catch (const std::invalid_argument &e) {
+        ERROR_LOG("Invalid metadata cache size argument: %s", optarg);
+      } catch (const std::out_of_range &e) {
+        ERROR_LOG("Metadata cache size out of range: %s", optarg);
+      }
+    } break;
+    case 'x': {
+      stat_prefix = optarg;
+      DEBUG_LOG("Stat prefix: %s", stat_prefix.c_str());
+    } break;
     default:
       break;
     }
@@ -256,6 +283,15 @@ Config::fromArgs(int const argc, char const *const argv[])
     DEBUG_LOG("Using default slice skip header %s", m_skip_header.c_str());
   }
 
+  if (m_min_size_to_slice > 0) {
+    if (m_oscache.has_value()) {
+      DEBUG_LOG("Metadata cache size: %zu", m_oscache->cache_capacity());
+    } else {
+      ERROR_LOG("--metadata-cache-size is required when --minimum-size is 
specified!  Using a default size of 16384.");
+      setCacheSize(16384);
+    }
+  }
+
   return true;
 }
 
@@ -309,3 +345,51 @@ Config::matchesRegex(char const *const url, int const 
urllen) const
 
   return matches;
 }
+
+void
+Config::setCacheSize(size_t entries)
+{
+  if (entries == 0) {
+    m_oscache.reset();
+  } else {
+    m_oscache.emplace(entries);
+  }
+}
+
+bool
+Config::isKnownLargeObj(std::string_view url)
+{
+  if (m_min_size_to_slice <= 0) {
+    // If conditional slicing is not set, all objects are large enough to slice
+    return true;
+  }
+
+  assert(m_oscache.has_value()); // object size cache is always present when 
conditionally slicing
+  std::optional<uint64_t> size = m_oscache->get(url);
+  if (size.has_value()) {
+    DEBUG_LOG("Found url in cache: %.*s -> %" PRIu64, 
static_cast<int>(url.size()), url.data(), size.value());
+    if (size.value() >= m_min_size_to_slice) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void
+Config::sizeCacheAdd(std::string_view url, uint64_t size)
+{
+  if (m_oscache) {
+    DEBUG_LOG("Adding url to cache: %.*s -> %" PRIu64, 
static_cast<int>(url.size()), url.data(), size);
+    m_oscache->set(url, size);
+  }
+}
+
+void
+Config::sizeCacheRemove(std::string_view url)
+{
+  if (m_oscache) {
+    DEBUG_LOG("Removing url from cache: %.*s", static_cast<int>(url.size()), 
url.data());
+    m_oscache->remove(url);
+  }
+}
diff --git a/plugins/slice/Config.h b/plugins/slice/Config.h
index c1e630447a..b7694a861a 100644
--- a/plugins/slice/Config.h
+++ b/plugins/slice/Config.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include "slice.h"
+#include "ObjectSizeCache.h"
 
 #ifdef HAVE_PCRE_PCRE_H
 #include <pcre/pcre.h>
@@ -45,8 +46,9 @@ struct Config {
   int         m_paceerrsecs{0};   // -1 disable logging, 0 no pacing, max 60s
   int         m_prefetchcount{0}; // 0 disables prefetching
   enum RefType { First, Relative };
-  RefType m_reftype{First};          // reference slice is relative to request
-  bool    m_head_strip_range{false}; // strip range header for head requests
+  RefType  m_reftype{First};          // reference slice is relative to request
+  bool     m_head_strip_range{false}; // strip range header for head requests
+  uint64_t m_min_size_to_slice{0};    // Only strip objects larger than this
 
   std::string m_skip_header;
   std::string m_crr_ims_header;
@@ -73,7 +75,23 @@ struct Config {
   // If no null reg, true, otherwise check against regex
   bool matchesRegex(char const *const url, int const urllen) const;
 
+  // Add an object size to cache
+  void sizeCacheAdd(std::string_view url, uint64_t size);
+
+  // Remove an object size
+  void sizeCacheRemove(std::string_view url);
+
+  // Did we cache this internally as a small object?
+  bool isKnownLargeObj(std::string_view url);
+
+  // Metadata cache stats
+  std::string stat_prefix{};
+  int         stat_TP{0}, stat_TN{0}, stat_FP{0}, stat_FN{0}, stat_no_cl{0}, 
stat_bad_cl{0}, stat_no_url{0};
+  bool        stats_enabled{false};
+
 private:
-  TSHRTime   m_nextlogtime{0}; // next time to log in ns
-  std::mutex m_mutex;
+  TSHRTime                       m_nextlogtime{0}; // next time to log in ns
+  std::mutex                     m_mutex;
+  std::optional<ObjectSizeCache> m_oscache;
+  void                           setCacheSize(size_t entries);
 };
diff --git a/plugins/slice/ObjectSizeCache.cc b/plugins/slice/ObjectSizeCache.cc
new file mode 100644
index 0000000000..2b7cec84b6
--- /dev/null
+++ b/plugins/slice/ObjectSizeCache.cc
@@ -0,0 +1,116 @@
+/** @file cache.cc
+
+  Metadata cache to store object sizes.
+
+  @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 "ObjectSizeCache.h"
+#include <cassert>
+
+ObjectSizeCache::ObjectSizeCache(cache_size_type cache_size)
+  : _cache_capacity(cache_size), _urls(cache_size), _object_sizes(cache_size, 
0), _visits(cache_size, false)
+{
+}
+
+std::optional<uint64_t>
+ObjectSizeCache::get(const std::string_view url)
+{
+  std::lock_guard lock{_mutex};
+  if (auto it = _index.find(url); it != _index.end()) {
+    // Cache hit
+    cache_size_type i = it->second;
+    _visits[i]        = true;
+    assert(url == _urls[i]);
+    return _object_sizes[i];
+  } else {
+    // Cache miss
+    return std::nullopt;
+  }
+}
+
+void
+ObjectSizeCache::set(const std::string_view url, uint64_t object_size)
+{
+  std::lock_guard lock{_mutex};
+  cache_size_type i;
+  if (auto it = _index.find(url); it != _index.end()) {
+    // Already exists in cache.  Overwrite.
+    i = it->second;
+  } else {
+    // Doesn't exist in cache.  Evict something else.
+    find_eviction_slot();
+    i                = _hand;
+    _urls[i]         = url;
+    _index[_urls[i]] = _hand;
+    _hand++;
+    if (_hand >= _cache_capacity) {
+      _hand = 0;
+    }
+  }
+  _object_sizes[i] = object_size;
+}
+
+void
+ObjectSizeCache::remove(const std::string_view url)
+{
+  std::lock_guard lock{_mutex};
+  if (auto it = _index.find(url); it != _index.end()) {
+    cache_size_type i = it->second;
+    _visits[i]        = false;
+    _urls[i].erase();
+    _index.erase(it);
+  }
+}
+
+/**
+ * @brief Make _hand point to the next entry that should be replaced, and 
clear that entry if it exists.
+ *
+ */
+void
+ObjectSizeCache::find_eviction_slot()
+{
+  while (_visits[_hand]) {
+    _visits[_hand] = false;
+    _hand++;
+    if (_hand >= _cache_capacity) {
+      _hand = 0;
+    }
+  }
+
+  std::string_view evicted_url = _urls[_hand];
+  if (!evicted_url.empty()) {
+    auto it = _index.find(evicted_url);
+    assert(it != _index.end());
+    _index.erase(it);
+    _urls[_hand].erase();
+  }
+}
+
+ObjectSizeCache::cache_size_type
+ObjectSizeCache::cache_capacity()
+{
+  return _cache_capacity;
+}
+
+ObjectSizeCache::cache_size_type
+ObjectSizeCache::cache_count()
+{
+  return _index.size();
+}
diff --git a/plugins/slice/ObjectSizeCache.h b/plugins/slice/ObjectSizeCache.h
new file mode 100644
index 0000000000..af3f5d5ab1
--- /dev/null
+++ b/plugins/slice/ObjectSizeCache.h
@@ -0,0 +1,78 @@
+/** @file cache.h
+
+  Metadata cache to store object sizes.
+
+  @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 <optional>
+#include <vector>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <mutex>
+
+class ObjectSizeCache
+{
+public:
+  using cache_size_type  = size_t;
+  using object_size_type = uint64_t;
+
+  ObjectSizeCache(cache_size_type capacity);
+
+  /**
+   * @brief Get an object size from cache.
+   *
+   * @param url The URL of the object
+   * @return std::optional<uint64_t> If the object size was found, return the 
size of the object.  If not, return std::nullopt.
+   */
+  std::optional<object_size_type> get(const std::string_view url);
+
+  /**
+   * @brief Add an object size to cache.
+   *
+   * @param url The URL of the object
+   * @param object_size The size of the object
+   */
+  void set(const std::string_view url, object_size_type object_size);
+
+  /**
+   * @brief Remove an object from cache
+   *
+   * @param url The URL of the object
+   */
+  void remove(const std::string_view url);
+
+  // Max capacity of the cache
+  cache_size_type cache_capacity();
+
+  // Current number of used entries in the cache
+  cache_size_type cache_count();
+
+private:
+  void find_eviction_slot();
+
+  cache_size_type                                       _cache_capacity;
+  cache_size_type                                       _hand{0};
+  std::vector<std::string>                              _urls;
+  std::vector<object_size_type>                         _object_sizes;
+  std::vector<bool>                                     _visits;
+  std::unordered_map<std::string_view, cache_size_type> _index;
+  std::mutex                                            _mutex;
+};
diff --git a/plugins/slice/server.cc b/plugins/slice/server.cc
index 2770d2a85d..bad9947994 100644
--- a/plugins/slice/server.cc
+++ b/plugins/slice/server.cc
@@ -23,6 +23,7 @@
 #include "HttpHeader.h"
 #include "response.h"
 #include "transfer.h"
+#include "ts/apidefs.h"
 #include "util.h"
 
 #include <cinttypes>
@@ -87,6 +88,31 @@ enum HeaderState {
   Passthru,
 };
 
+static void
+update_object_size(TSHttpTxn txnp, int64_t size, Config &config)
+{
+  int   urllen = 0;
+  char *urlstr = TSHttpTxnEffectiveUrlStringGet(txnp, &urllen);
+  if (urlstr != nullptr) {
+    if (size <= 0) {
+      DEBUG_LOG("Ignoring invalid content length for %.*s: %" PRId64, urllen, 
urlstr, size);
+      return;
+    }
+
+    if (static_cast<uint64_t>(size) >= config.m_min_size_to_slice) {
+      config.sizeCacheAdd({urlstr, static_cast<size_t>(urllen)}, 
static_cast<uint64_t>(size));
+      TSStatIntIncrement(config.stat_TP, 1);
+    } else {
+      config.sizeCacheRemove({urlstr, static_cast<size_t>(urllen)});
+      TSStatIntIncrement(config.stat_FP, 1);
+    }
+
+    TSfree(urlstr);
+  } else {
+    ERROR_LOG("Could not get URL from transaction.");
+  }
+}
+
 HeaderState
 handleFirstServerHeader(Data *const data, TSCont const contp)
 {
@@ -121,6 +147,7 @@ handleFirstServerHeader(Data *const data, TSCont const 
contp)
     }
     DEBUG_LOG("Passthru bytes: header: %" PRId64 " body: %" PRId64, hlen, 
clen);
     if (clen != INT64_MAX) {
+      update_object_size(data->m_txnp, clen, *data->m_config);
       TSVIONBytesSet(output_vio, hlen + clen);
     } else {
       TSVIONBytesSet(output_vio, clen);
@@ -140,6 +167,8 @@ handleFirstServerHeader(Data *const data, TSCont const 
contp)
     return HeaderState::Fail;
   }
 
+  update_object_size(data->m_txnp, blockcr.m_length, *data->m_config);
+
   // set the resource content length from block response
   data->m_contentlen = blockcr.m_length;
 
diff --git a/plugins/slice/slice.cc b/plugins/slice/slice.cc
index 0b62658262..515a0ed81d 100644
--- a/plugins/slice/slice.cc
+++ b/plugins/slice/slice.cc
@@ -23,18 +23,45 @@
 #include "HttpHeader.h"
 #include "intercept.h"
 
+#include "ts/apidefs.h"
 #include "ts/remap.h"
 #include "ts/ts.h"
 
+#include <charconv>
 #include <netinet/in.h>
-#include <mutex>
+#include <array>
+#include <string_view>
 
 namespace
 {
+using namespace std::string_view_literals;
+constexpr std::string_view SKIP_CRR_HDR_NAME  = "X-Skip-Crr"sv;
+constexpr std::string_view SKIP_CRR_HDR_VALUE = "-"sv;
+
+struct PluginInfo {
+  Config config;
+  TSCont read_resp_hdr_contp;
+};
+
 Config globalConfig;
+TSCont global_read_resp_hdr_contp;
+
+static bool
+should_skip_this_obj(TSHttpTxn txnp, Config *const config)
+{
+  int         len    = 0;
+  char *const urlstr = TSHttpTxnEffectiveUrlStringGet(txnp, &len);
+
+  if (!config->isKnownLargeObj({urlstr, static_cast<size_t>(len)})) {
+    DEBUG_LOG("Not a known large object, not slicing: %.*s", len, urlstr);
+    return true;
+  }
+
+  return false;
+}
 
 bool
-read_request(TSHttpTxn txnp, Config *const config)
+read_request(TSHttpTxn txnp, Config *const config, TSCont read_resp_hdr_contp)
 {
   DEBUG_LOG("slice read_request");
   TxnHdrMgr hdrmgr;
@@ -66,11 +93,6 @@ read_request(TSHttpTxn txnp, Config *const config)
         }
       }
 
-      // turn off any and all transaction caching (shouldn't matter)
-      TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SERVER_NO_STORE, true);
-      TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_RESPONSE_CACHEABLE, false);
-      TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_REQUEST_CACHEABLE, false);
-
       DEBUG_LOG("slice accepting and slicing");
       // connection back into ATS
       sockaddr const *const ip = TSHttpTxnClientAddrGet(txnp);
@@ -79,7 +101,7 @@ read_request(TSHttpTxn txnp, Config *const config)
       }
 
       TSAssert(nullptr != config);
-      Data *const data = new Data(config);
+      std::unique_ptr<Data> data = std::make_unique<Data>(config);
 
       data->m_method_type = header.method();
       data->m_txnp        = txnp;
@@ -90,7 +112,6 @@ read_request(TSHttpTxn txnp, Config *const config)
       } else if (AF_INET6 == ip->sa_family) {
         memcpy(&data->m_client_ip, ip, sizeof(sockaddr_in6));
       } else {
-        delete data;
         return false;
       }
 
@@ -98,7 +119,21 @@ read_request(TSHttpTxn txnp, Config *const config)
       data->m_hostlen = sizeof(data->m_hostname) - 1;
       if (!header.valueForKey(TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST, 
data->m_hostname, &data->m_hostlen)) {
         DEBUG_LOG("Unable to get hostname from header");
-        delete data;
+        return false;
+      }
+
+      // check if object is large enough to slice - skip small and unknown 
size objects
+      if (should_skip_this_obj(txnp, config)) {
+        TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, 
read_resp_hdr_contp);
+        TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, 
read_resp_hdr_contp);
+
+        // If the client sends a range request, and we don't slice, the range 
request goes to cache_range_requests, and can be
+        // cached as a range request. We don't want that, and instead want to 
skip CRR entirely.
+        if (header.hasKey(TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE)) {
+          HttpHeader mutable_header(hdrmgr.m_buffer, hdrmgr.m_lochdr);
+          mutable_header.setKeyVal(SKIP_CRR_HDR_NAME.data(), 
SKIP_CRR_HDR_NAME.length(), SKIP_CRR_HDR_VALUE.data(),
+                                   SKIP_CRR_HDR_VALUE.length());
+        }
         return false;
       }
 
@@ -118,7 +153,6 @@ read_request(TSHttpTxn txnp, Config *const config)
           if (TS_SUCCESS != rcode) {
             ERROR_LOG("Error cloning pristine url");
             TSMBufferDestroy(newbuf);
-            delete data;
             return false;
           }
 
@@ -152,7 +186,6 @@ read_request(TSHttpTxn txnp, Config *const config)
               TSHandleMLocRelease(newbuf, nullptr, newloc);
             }
             TSMBufferDestroy(newbuf);
-            delete data;
             return false;
           }
 
@@ -174,7 +207,7 @@ read_request(TSHttpTxn txnp, Config *const config)
       // we'll intercept this GET and do it ourselves
       TSMutex const mutex = TSContMutexGet(reinterpret_cast<TSCont>(txnp));
       TSCont const  icontp(TSContCreate(intercept_hook, mutex));
-      TSContDataSet(icontp, (void *)data);
+      TSContDataSet(icontp, data.release());
 
       // Skip remap and remap rule requirement
       TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SKIP_REMAPPING, true);
@@ -191,6 +224,60 @@ read_request(TSHttpTxn txnp, Config *const config)
   return false;
 }
 
+static int
+read_resp_hdr(TSCont contp, TSEvent event, void *edata)
+{
+  TSHttpTxn   txnp = static_cast<TSHttpTxn>(edata);
+  PluginInfo *info = static_cast<PluginInfo *>(TSContDataGet(contp));
+
+  // This function does the following things:
+  // 1. Parse the object size from Content-Length
+  // 2. Cache the object size
+  // 3. If the object will be sliced in subsequent requests, turn off the 
cache to avoid taking up space, and head-of-line blocking.
+
+  int   urllen = 0;
+  char *urlstr = TSHttpTxnEffectiveUrlStringGet(txnp, &urllen);
+  if (urlstr != nullptr) {
+    TxnHdrMgr                response;
+    TxnHdrMgr::HeaderGetFunc func = event == 
TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE ? TSHttpTxnCachedRespGet : 
TSHttpTxnServerRespGet;
+    response.populateFrom(txnp, func);
+    HttpHeader const resp_header(response.m_buffer, response.m_lochdr);
+    char             constr[1024];
+    int              conlen = sizeof constr;
+    bool const 
hasContentLength(resp_header.valueForKey(TS_MIME_FIELD_CONTENT_LENGTH, 
TS_MIME_LEN_CONTENT_LENGTH, constr, &conlen));
+    if (hasContentLength) {
+      uint64_t content_length;
+
+      [[maybe_unused]] auto [ptr, ec] = std::from_chars(constr, constr + 
conlen, content_length);
+      if (ec == std::errc()) {
+        if (content_length >= info->config.m_min_size_to_slice) {
+          // Remember that this object is big
+          info->config.sizeCacheAdd({urlstr, static_cast<size_t>(urllen)}, 
content_length);
+          // This object will be sliced in future requests.  Don't cache it 
for now.
+          TSHttpTxnServerRespNoStoreSet(txnp, 1);
+          TSStatIntIncrement(info->config.stat_FN, 1);
+        } else {
+          TSStatIntIncrement(info->config.stat_TN, 1);
+        }
+      } else {
+        ERROR_LOG("Could not parse content-length: %.*s", conlen, constr);
+        TSStatIntIncrement(info->config.stat_bad_cl, 1);
+      }
+    } else {
+      DEBUG_LOG("Could not get a content length for updating object size");
+      TSStatIntIncrement(info->config.stat_no_cl, 1);
+    }
+    TSfree(urlstr);
+  } else {
+    ERROR_LOG("Could not get URL for obj size.");
+    TSStatIntIncrement(info->config.stat_no_url, 1);
+  }
+
+  // Reenable and continue with the state machine.
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+  return 0;
+}
+
 int
 global_read_request_hook(TSCont // contp
                          ,
@@ -199,7 +286,7 @@ global_read_request_hook(TSCont // contp
                          void *edata)
 {
   TSHttpTxn const txnp = static_cast<TSHttpTxn>(edata);
-  read_request(txnp, &globalConfig);
+  read_request(txnp, &globalConfig, global_read_resp_hdr_contp);
   TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
   return 0;
 }
@@ -216,9 +303,9 @@ DbgCtl dbg_ctl{PLUGIN_NAME};
 TSRemapStatus
 TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri 
ATS_UNUSED */)
 {
-  Config *const config = static_cast<Config *>(ih);
+  PluginInfo *const info = static_cast<PluginInfo *>(ih);
 
-  if (read_request(txnp, config)) {
+  if (read_request(txnp, &info->config, info->read_resp_hdr_contp)) {
     return TSREMAP_DID_REMAP_STOP;
   } else {
     return TSREMAP_NO_REMAP;
@@ -231,12 +318,61 @@ TSRemapOSResponse(void * /* ih ATS_UNUSED */, TSHttpTxn 
/* rh ATS_UNUSED */, int
 {
 }
 
+static bool
+register_stat(const char *name, int &id)
+{
+  if (TSStatFindName(name, &id) == TS_ERROR) {
+    id = TSStatCreate(name, TS_RECORDDATATYPE_INT, TS_STAT_NON_PERSISTENT, 
TS_STAT_SYNC_SUM);
+    if (id == TS_ERROR) {
+      ERROR_LOG("Failed to register stat '%s'", name);
+      return false;
+    }
+  }
+
+  DEBUG_LOG("[%s] %s registered with id %d", PLUGIN_NAME, name, id);
+
+  return true;
+}
+
+static void
+init_stats(Config &config, const std::string &prefix)
+{
+  const std::array<std::pair<const char *, int &>, 7> stats{
+    {
+     {".metadata_cache.true_large_objects", config.stat_TP},
+     {".metadata_cache.true_small_objects", config.stat_TN},
+     {".metadata_cache.false_large_objects", config.stat_FP},
+     {".metadata_cache.false_small_objects", config.stat_FN},
+     {".metadata_cache.no_content_length", config.stat_no_cl},
+     {".metadata_cache.bad_content_length", config.stat_bad_cl},
+     {".metadata_cache.no_url", config.stat_no_url},
+     }
+  };
+
+  config.stats_enabled = true;
+  for (const auto &stat : stats) {
+    const std::string name  = std::string{PLUGIN_NAME "."} + prefix + 
stat.first;
+    config.stats_enabled   &= register_stat(name.c_str(), stat.second);
+  }
+}
+
 TSReturnCode
 TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf */, int 
/* errbuf_size */)
 {
-  Config *const config = new Config;
-  config->fromArgs(argc - 2, argv + 2);
-  *ih = static_cast<void *>(config);
+  PluginInfo *const info = new PluginInfo;
+
+  info->config.fromArgs(argc - 2, argv + 2);
+
+  TSCont read_resp_hdr_contp = TSContCreate(read_resp_hdr, nullptr);
+  TSContDataSet(read_resp_hdr_contp, static_cast<void *>(info));
+  info->read_resp_hdr_contp = read_resp_hdr_contp;
+
+  if (!info->config.stat_prefix.empty()) {
+    init_stats(info->config, info->config.stat_prefix);
+  }
+
+  *ih = static_cast<void *>(info);
+
   return TS_SUCCESS;
 }
 
@@ -244,8 +380,9 @@ void
 TSRemapDeleteInstance(void *ih)
 {
   if (nullptr != ih) {
-    Config *const config = static_cast<Config *>(ih);
-    delete config;
+    PluginInfo *const info = static_cast<PluginInfo *>(ih);
+    TSContDestroy(info->read_resp_hdr_contp);
+    delete info;
   }
 }
 
@@ -273,8 +410,12 @@ TSPluginInit(int argc, char const *argv[])
 
   globalConfig.fromArgs(argc - 1, argv + 1);
 
-  TSCont const contp(TSContCreate(global_read_request_hook, nullptr));
+  TSCont const 
global_read_request_contp(TSContCreate(global_read_request_hook, nullptr));
+  global_read_resp_hdr_contp = TSContCreate(read_resp_hdr, nullptr);
 
   // Called immediately after the request header is read from the client
-  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, contp);
+  TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, global_read_request_contp);
+
+  // Register stats for metadata cache
+  init_stats(globalConfig, "global");
 }
diff --git a/plugins/slice/unit-tests/CMakeLists.txt 
b/plugins/slice/unit-tests/CMakeLists.txt
index 9ddec81ee6..b616af17a7 100644
--- a/plugins/slice/unit-tests/CMakeLists.txt
+++ b/plugins/slice/unit-tests/CMakeLists.txt
@@ -25,7 +25,12 @@ target_compile_definitions(test_range PRIVATE UNITTEST)
 target_link_libraries(test_range PRIVATE catch2::catch2 ts::tsutil)
 add_test(NAME test_range COMMAND test_range)
 
-add_executable(test_config test_config.cc ${PROJECT_SOURCE_DIR}/Config.cc)
+add_executable(test_config test_config.cc ${PROJECT_SOURCE_DIR}/Config.cc 
${PROJECT_SOURCE_DIR}/ObjectSizeCache.cc)
 target_compile_definitions(test_config PRIVATE UNITTEST)
 target_link_libraries(test_config PRIVATE PCRE::PCRE catch2::catch2 ts::tsutil)
 add_test(NAME test_config COMMAND test_config)
+
+add_executable(test_cache test_cache.cc 
${PROJECT_SOURCE_DIR}/ObjectSizeCache.cc)
+target_compile_definitions(test_cache PRIVATE UNITTEST)
+target_link_libraries(test_cache PRIVATE catch2::catch2 ts::tsutil)
+add_test(NAME test_cache COMMAND test_cache)
diff --git a/plugins/slice/unit-tests/test_cache.cc 
b/plugins/slice/unit-tests/test_cache.cc
new file mode 100644
index 0000000000..2c74433751
--- /dev/null
+++ b/plugins/slice/unit-tests/test_cache.cc
@@ -0,0 +1,173 @@
+/** @file test_cache.cc
+
+  Unit tests for metadata cache
+
+  @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 <optional>
+#include <random>
+#include <sstream>
+#include <thread>
+#include <atomic>
+#define CATCH_CONFIG_MAIN /* include main function */
+#include "catch.hpp"      /* catch unit-test framework */
+#include "../ObjectSizeCache.h"
+
+using namespace std::string_view_literals;
+TEST_CASE("cache miss", "[slice][metadatacache]")
+{
+  ObjectSizeCache cache{1024};
+  std::optional   res = cache.get("example.com"sv);
+  CHECK(res == std::nullopt);
+}
+
+TEST_CASE("cache hit", "[slice][metadatacache]")
+{
+  ObjectSizeCache cache{1024};
+  cache.set("example.com/123"sv, 123);
+  std::optional res2 = cache.get("example.com/123"sv);
+  CHECK(res2.value() == 123);
+}
+
+TEST_CASE("cache remove", "[slice][metadatacache]")
+{
+  ObjectSizeCache cache{1024};
+  cache.set("example.com/123"sv, 123);
+  std::optional res2 = cache.get("example.com/123"sv);
+  CHECK(res2.value() == 123);
+  cache.remove("example.com/123"sv);
+  std::optional res3 = cache.get("example.com/123"sv);
+  REQUIRE(!res3.has_value());
+  REQUIRE(cache.cache_count() == 0);
+  REQUIRE(cache.cache_capacity() == 1024);
+}
+
+TEST_CASE("eviction", "[slice][metadatacache]")
+{
+  constexpr int   cache_size = 10;
+  ObjectSizeCache cache{cache_size};
+  for (uint64_t i = 0; i < cache_size * 100; i++) {
+    std::stringstream ss;
+    ss << "http://example.com/"; << i;
+    cache.set(ss.str(), i);
+  }
+  size_t found = 0;
+  for (uint64_t i = 0; i < cache_size * 100; i++) {
+    std::stringstream ss;
+    ss << "http://example.com/"; << i;
+    std::optional<uint64_t> size = cache.get(ss.str());
+    if (size.has_value()) {
+      CHECK(size.value() == i);
+      found++;
+    }
+  }
+  REQUIRE(found == cache_size);
+}
+
+TEST_CASE("tiny cache", "[slice][metadatacache]")
+{
+  constexpr int   cache_size = 1;
+  ObjectSizeCache cache{cache_size};
+  for (uint64_t i = 0; i < cache_size * 100; i++) {
+    std::stringstream ss;
+    ss << "http://example.com/"; << i;
+    cache.set(ss.str(), i);
+  }
+  size_t found = 0;
+  for (uint64_t i = 0; i < cache_size * 100; i++) {
+    std::stringstream ss;
+    ss << "http://example.com/"; << i;
+    std::optional<uint64_t> size = cache.get(ss.str());
+    if (size.has_value()) {
+      CHECK(size.value() == i);
+      found++;
+    }
+  }
+  REQUIRE(found == cache_size);
+}
+
+TEST_CASE("hit rate", "[slice][metadatacache]")
+{
+  constexpr int                       cache_size = 10;
+  ObjectSizeCache                     cache{cache_size};
+  std::mt19937                        gen;
+  std::poisson_distribution<uint64_t> d{cache_size};
+  std::atomic<int>                    hits{0}, misses{0};
+
+  for (uint64_t i = 0; i < cache_size * 100; i++) {
+    std::stringstream ss;
+    uint64_t          obj = d(gen);
+
+    ss << "http://example.com/"; << obj;
+    std::optional<uint64_t> size = cache.get(ss.str());
+    if (size.has_value()) {
+      CHECK(size.value() == obj);
+      hits++;
+    } else {
+      cache.set(ss.str(), obj);
+      misses++;
+    }
+  }
+
+  INFO("Hits: " << hits);
+  INFO("Misses: " << misses);
+  REQUIRE(hits > cache_size * 50);
+}
+
+TEST_CASE("threads", "[slice][metadatacache]")
+{
+  constexpr int   cache_size = 10;
+  ObjectSizeCache cache{cache_size};
+
+  std::mt19937                        gen;
+  std::poisson_distribution<uint64_t> d{cache_size};
+  std::vector<std::thread>            threads;
+  std::atomic<int>                    hits{0}, misses{0};
+
+  auto runfunc = [&]() {
+    for (uint64_t i = 0; i < cache_size * 100; i++) {
+      std::stringstream ss;
+      uint64_t          obj = d(gen);
+
+      ss << "http://example.com/"; << obj;
+      std::optional<uint64_t> size = cache.get(ss.str());
+      if (size.has_value()) {
+        CHECK(size.value() == obj);
+        hits++;
+      } else {
+        cache.set(ss.str(), obj);
+        misses++;
+      }
+    }
+  };
+
+  for (int i = 0; i < 4; i++) {
+    threads.emplace_back(runfunc);
+  }
+
+  for (auto &t : threads) {
+    t.join();
+  }
+  INFO("Hits: " << hits);
+  INFO("Misses: " << misses);
+  REQUIRE(hits > cache_size * 50 * 4);
+  REQUIRE(cache.cache_count() == cache_size);
+  REQUIRE(cache.cache_capacity() == cache_size);
+}
diff --git a/tests/gold_tests/pluginTest/slice/slice_conditional.test.py 
b/tests/gold_tests/pluginTest/slice/slice_conditional.test.py
new file mode 100644
index 0000000000..fe39152979
--- /dev/null
+++ b/tests/gold_tests/pluginTest/slice/slice_conditional.test.py
@@ -0,0 +1,156 @@
+'''
+'''
+#  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.
+
+Test.Summary = '''
+Conditional Slicing test
+'''
+
+# Test description:
+# Preload the cache with the entire asset to be range requested.
+# Reload remap rule with slice plugin
+# Request content through the slice plugin
+
+Test.SkipUnless(
+    Condition.PluginExists('slice.so'),
+    Condition.PluginExists('cache_range_requests.so'),
+    Condition.PluginExists('xdebug.so'),
+)
+Test.ContinueOnFail = False
+
+# configure origin server
+server = Test.MakeOriginServer("server", lookup_key="{PATH}{%Range}", 
options={'-v': None})
+
+# Define ATS and configure
+ts = Test.MakeATSProcess("ts")
+
+# small object, should not be sliced
+req_small = {
+    "headers": "GET /small HTTP/1.1\r\n" + "Host: www.example.com\r\n" + 
"\r\n",
+    "body": "",
+}
+res_small = {
+    "headers": "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + 
"Cache-Control: max-age=10,public\r\n" + "\r\n",
+    "body": "smol",
+}
+server.addResponse("sessionlog.json", req_small, res_small)
+
+# large object, all in one slice
+req_large = {
+    "headers": "GET /large HTTP/1.1\r\n" + "Host: www.example.com\r\n" + 
"\r\n",
+    "body": "",
+}
+res_large = {
+    "headers": "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + 
"Cache-Control: max-age=10,public\r\n" + "\r\n",
+    "body": "unsliced large object!"
+}
+server.addResponse("sessionlog.json", req_large, res_large)
+
+# large object, this populates the individual slices in the server
+
+large_body = "large object sliced!"
+body_len = len(large_body)
+slice_begin = 0
+slice_block_size = 10
+while (slice_begin < body_len):
+    slice_end = slice_begin + slice_block_size
+    req_large_slice = {
+        "headers": "GET /large HTTP/1.1\r\n" + "Host: www.example.com\r\n" + 
f"Range: bytes={slice_begin}-{slice_end - 1}" + "\r\n",
+        "body": "",
+    }
+    if slice_end > body_len:
+        slice_end = body_len
+    res_large_slice = {
+        "headers":
+            "HTTP/1.1 206 Partial Content\r\n" + "Connection: close\r\n" + 
"Accept-Ranges: bytes\r\n" +
+            f"Content-Range: bytes {slice_begin}-{slice_end - 
1}/{body_len}\r\n" + "Cache-Control: max-age=10,public\r\n" + "\r\n",
+        "body": large_body[slice_begin:slice_end]
+    }
+    server.addResponse("sessionlog.json", req_large_slice, res_large_slice)
+    slice_begin += slice_block_size
+
+# set up slice plugin with remap host into cache_range_requests
+ts.Disk.remap_config.AddLines(
+    [
+        f'map http://slice/ http://127.0.0.1:{server.Variables.Port}/' +
+        f' @plugin=slice.so @pparam=--blockbytes-test={slice_block_size} 
@pparam=--minimum-size=8 @pparam=--metadata-cache-size=4 
@plugin=cache_range_requests.so'
+    ])
+
+ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache')
+ts.Disk.records_config.update(
+    {
+        'proxy.config.diags.debug.enabled': '0',
+        'proxy.config.diags.debug.tags': 
'http|cache|slice|xdebug|cache_range_requests',
+    })
+
+curl_and_args = 'curl -s -D /dev/stdout -o /dev/stderr -x 
localhost:{}'.format(ts.Variables.port) + ' -H "x-debug: x-cache"'
+
+# Test case: first request of small object
+tr = Test.AddTestRun("Small request 1")
+ps = tr.Processes.Default
+ps.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
+ps.StartBefore(Test.Processes.ts)
+ps.Command = curl_and_args + ' http://slice/small'
+ps.ReturnCode = 0
+ps.Streams.stderr.Content = Testers.ContainsExpression('smol', 'expected smol')
+ps.Streams.stdout.Content = Testers.ContainsExpression('X-Cache: miss', 
'expected cache miss')
+tr.StillRunningAfter = ts
+
+# Test case: second request of small object - expect cache hit
+tr = Test.AddTestRun("Small request 2")
+ps = tr.Processes.Default
+ps.Command = curl_and_args + ' http://slice/small'
+ps.ReturnCode = 0
+ps.Streams.stderr.Content = Testers.ContainsExpression('smol', 'expected smol')
+ps.Streams.stdout.Content = Testers.ContainsExpression('X-Cache: hit-fresh', 
'expected cache hit-fresh')
+tr.StillRunningAfter = ts
+
+# Test case: range request of small object - expect cache hit 
(proxy.config.http.cache.range.lookup = 1)
+tr = Test.AddTestRun("Small request - ranged")
+ps = tr.Processes.Default
+ps.Command = curl_and_args + ' -r 1-2 http://slice/small'
+ps.ReturnCode = 0
+ps.Streams.stderr.Content = Testers.ContainsExpression('mo', 'expected mo')
+ps.Streams.stdout.Content = Testers.ContainsExpression('X-Cache: hit-fresh', 
'expected cache hit-fresh')
+tr.StillRunningAfter = ts
+
+# Test case: first request of large object - expect unsliced, cache write 
disabled
+tr = Test.AddTestRun("Large request 1")
+ps = tr.Processes.Default
+ps.Command = curl_and_args + ' http://slice/large'
+ps.ReturnCode = 0
+ps.Streams.stderr.Content = Testers.ContainsExpression('unsliced large 
object!', 'expected large object')
+ps.Streams.stdout.Content = Testers.ContainsExpression('X-Cache: miss', 
'expected cache miss')
+tr.StillRunningAfter = ts
+
+# Test case: first request of large object - expect sliced, cache miss
+tr = Test.AddTestRun("Large request 2")
+ps = tr.Processes.Default
+ps.Command = curl_and_args + ' http://slice/large'
+ps.ReturnCode = 0
+ps.Streams.stderr.Content = Testers.ContainsExpression('large object sliced!', 
'expected large object')
+ps.Streams.stdout.Content = Testers.ContainsExpression('X-Cache: miss', 
'expected cache miss')
+tr.StillRunningAfter = ts
+
+## Test case: first request of large object - expect cache hit
+tr = Test.AddTestRun("Large request 3")
+ps = tr.Processes.Default
+ps.Command = curl_and_args + ' http://slice/large'
+ps.ReturnCode = 0
+ps.Streams.stderr.Content = Testers.ContainsExpression('large object sliced!', 
'expected large object')
+ps.Streams.stdout.Content = Testers.ContainsExpression('X-Cache: hit-fresh', 
'expected cache hit-fresh')
+tr.StillRunningAfter = ts


Reply via email to