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 f1247af0c3 Add USDT for cache directory insertion and deletion (#12688)
f1247af0c3 is described below

commit f1247af0c38629b2bb52958596b485e3c0acd4a5
Author: Mo Chen <[email protected]>
AuthorDate: Thu Nov 27 20:06:54 2025 -0600

    Add USDT for cache directory insertion and deletion (#12688)
---
 src/iocore/cache/CacheDir.cc                       |  23 +++-
 src/iocore/cache/P_CacheDir.h                      |   6 +-
 tests/gold_tests/ats_probe/cache_dir_probe.bt      |  44 ++++++++
 tests/gold_tests/ats_probe/cache_dir_probe.test.py | 122 +++++++++++++++++++++
 4 files changed, 187 insertions(+), 8 deletions(-)

diff --git a/src/iocore/cache/CacheDir.cc b/src/iocore/cache/CacheDir.cc
index 76b32a298e..0eddb1827b 100644
--- a/src/iocore/cache/CacheDir.cc
+++ b/src/iocore/cache/CacheDir.cc
@@ -30,6 +30,7 @@
 
 #include "tscore/hugepages.h"
 #include "tscore/Random.h"
+#include "ts/ats_probe.h"
 
 #ifdef LOOP_CHECK_MODE
 #define DIR_LOOP_THRESHOLD 1000
@@ -327,6 +328,9 @@ dir_delete_entry(Dir *e, Dir *p, int s, Directory 
*directory)
   } else {
     Dir *n = next_dir(e, seg);
     if (n) {
+      // "Shuffle" here means that we're copying the second entry's data to 
the head entry's location, and removing the second entry
+      // - because the head entry can't be moved.
+      ATS_PROBE3(cache_dir_shuffle, s, dir_to_offset(e, seg), dir_to_offset(n, 
seg));
       dir_assign(e, n);
       dir_delete_entry(n, e, s, directory);
       return e;
@@ -339,7 +343,7 @@ dir_delete_entry(Dir *e, Dir *p, int s, Directory 
*directory)
 }
 
 inline void
-dir_clean_bucket(Dir *b, int s, Stripe *stripe)
+dir_clean_bucket(Dir *b, int s, StripeSM *stripe)
 {
   Dir *e = b, *p = nullptr;
   Dir *seg = stripe->directory.get_segment(s);
@@ -363,6 +367,8 @@ dir_clean_bucket(Dir *b, int s, Stripe *stripe)
         ts::Metrics::Gauge::decrement(cache_rsb.direntries_used);
         
ts::Metrics::Gauge::decrement(stripe->cache_vol->vol_rsb.direntries_used);
       }
+      // Match cache_dir_remove arguments
+      ATS_PROBE7(cache_dir_remove_clean_bucket, stripe->fd, s, 
dir_to_offset(e, seg), dir_offset(e), dir_approx_size(e), 0, 0);
       e = dir_delete_entry(e, p, s, &stripe->directory);
       continue;
     }
@@ -372,7 +378,7 @@ dir_clean_bucket(Dir *b, int s, Stripe *stripe)
 }
 
 void
-Directory::clean_segment(int s, Stripe *stripe)
+Directory::clean_segment(int s, StripeSM *stripe)
 {
   Dir *seg = this->get_segment(s);
   for (int64_t i = 0; i < this->buckets; i++) {
@@ -382,7 +388,7 @@ Directory::clean_segment(int s, Stripe *stripe)
 }
 
 void
-Directory::cleanup(Stripe *stripe)
+Directory::cleanup(StripeSM *stripe)
 {
   for (int64_t i = 0; i < this->segments; i++) {
     this->clean_segment(i, stripe);
@@ -391,7 +397,7 @@ Directory::cleanup(Stripe *stripe)
 }
 
 void
-Directory::clear_range(off_t start, off_t end, Stripe *stripe)
+Directory::clear_range(off_t start, off_t end, StripeSM *stripe)
 {
   for (off_t i = 0; i < this->entries(); i++) {
     Dir *e = dir_index(stripe, i);
@@ -522,6 +528,8 @@ Lagain:
         } else { // delete the invalid entry
           ts::Metrics::Gauge::decrement(cache_rsb.direntries_used);
           
ts::Metrics::Gauge::decrement(stripe->cache_vol->vol_rsb.direntries_used);
+          ATS_PROBE7(cache_dir_remove_invalid, stripe->fd, s, dir_to_offset(e, 
seg), dir_offset(e), dir_approx_size(e),
+                     key->slice64(0), key->slice64(1));
           e = dir_delete_entry(e, p, s, this);
           continue;
         }
@@ -605,6 +613,8 @@ Lfill:
   ink_assert(stripe->vol_offset(e) < (stripe->skip + stripe->len));
   DDbg(dbg_ctl_dir_insert, "insert %p %X into vol %d bucket %d at %p tag %X %X 
boffset %" PRId64 "", e, key->slice32(0), stripe->fd,
        bi, e, key->slice32(1), dir_tag(e), dir_offset(e));
+  ATS_PROBE7(cache_dir_insert, stripe->fd, s, dir_to_offset(e, seg), 
dir_offset(e), dir_approx_size(e), key->slice64(0),
+             key->slice64(1));
   CHECK_DIR(d);
   stripe->directory.header->dirty = 1;
   ts::Metrics::Gauge::increment(cache_rsb.direntries_used);
@@ -724,9 +734,12 @@ Directory::remove(const CacheKey *key, StripeSM *stripe, 
Dir *del)
           return 0;
       }
 #endif
-      if (dir_compare_tag(e, key) && dir_offset(e) == dir_offset(del)) {
+      int64_t offset = dir_offset(e);
+      if (dir_compare_tag(e, key) && offset == dir_offset(del)) {
         ts::Metrics::Gauge::decrement(cache_rsb.direntries_used);
         
ts::Metrics::Gauge::decrement(stripe->cache_vol->vol_rsb.direntries_used);
+        ATS_PROBE7(cache_dir_remove, stripe->fd, s, dir_to_offset(e, seg), 
offset, dir_approx_size(e), key->slice64(0),
+                   key->slice64(1));
         dir_delete_entry(e, p, s, this);
         CHECK_DIR(d);
         return 1;
diff --git a/src/iocore/cache/P_CacheDir.h b/src/iocore/cache/P_CacheDir.h
index 2ddbedc4f7..6f46118ef9 100644
--- a/src/iocore/cache/P_CacheDir.h
+++ b/src/iocore/cache/P_CacheDir.h
@@ -292,12 +292,12 @@ struct Directory {
   int      remove(const CacheKey *key, StripeSM *stripe, Dir *del);
   void     free_entry(Dir *e, int s);
   int      check();
-  void     cleanup(Stripe *stripe);
-  void     clear_range(off_t start, off_t end, Stripe *stripe);
+  void     cleanup(StripeSM *stripe);
+  void     clear_range(off_t start, off_t end, StripeSM *stripe);
   uint64_t entries_used();
   int      bucket_length(Dir *b, int s);
   int      freelist_length(int s);
-  void     clean_segment(int s, Stripe *stripe);
+  void     clean_segment(int s, StripeSM *stripe);
 };
 
 inline int
diff --git a/tests/gold_tests/ats_probe/cache_dir_probe.bt 
b/tests/gold_tests/ats_probe/cache_dir_probe.bt
new file mode 100644
index 0000000000..6e7159016d
--- /dev/null
+++ b/tests/gold_tests/ats_probe/cache_dir_probe.bt
@@ -0,0 +1,44 @@
+#!/usr/bin/env bpftrace
+/** @file
+
+  Trace cache directory SystemTap probes.
+
+  @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.
+ */
+
+BEGIN
+{
+  printf("cache_dir_probe: ready\n");
+}
+
+usdt:*:cache_dir_insert
+{
+  if (@inserted == 0) {
+    printf("cache_dir_insert\n");
+  }
+  @inserted = 1;
+}
+
+usdt:*:cache_dir_remove
+{
+  if (@removed == 0) {
+    printf("cache_dir_remove\n");
+  }
+  @removed = 1;
+}
diff --git a/tests/gold_tests/ats_probe/cache_dir_probe.test.py 
b/tests/gold_tests/ats_probe/cache_dir_probe.test.py
new file mode 100644
index 0000000000..3b7208c25f
--- /dev/null
+++ b/tests/gold_tests/ats_probe/cache_dir_probe.test.py
@@ -0,0 +1,122 @@
+'''Verify cache directory SystemTap probes fire on insert and remove.'''
+#  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.
+
+import os
+
+Test.Summary = '''Verify cache directory SystemTap probes fire on cache fill 
and PURGE.'''
+
+# Skipping this test generally because it requires privilege. Thus most CI 
systems will skip it.
+Test.SkipUnless(
+    Condition(lambda: os.geteuid() == 0, "Test requires privilege", True),
+    Condition.HasProgram("bpftrace", "Need bpftrace to verify the probe."))
+
+
+class CacheDirProbeTest:
+    '''Verify cache directory SystemTap probes.'''
+    bt_script: str = 'cache_dir_probe.bt'
+    _cache_path: str = '/cacheable'
+
+    def __init__(self):
+        tr = Test.AddTestRun('Cache directory probes should trigger on insert 
and purge.')
+        self._configure_origin(tr)
+        self._configure_traffic_server(tr)
+        self._configure_bpftrace(tr)
+        self._configure_client(tr)
+
+    def _configure_origin(self, tr: 'TestRun') -> 'Process':
+        '''Configure the origin microserver.'''
+        origin = Test.MakeOriginServer('origin')
+        self._origin = origin
+
+        cache_request = {
+            "headers": f"GET {self._cache_path} HTTP/1.1\r\nHost: 
cache-probe.test\r\n\r\n",
+            "timestamp": "1469733493.993",
+            "body": ""
+        }
+        cache_response = {
+            "headers": "HTTP/1.1 200 OK\r\nConnection: close\r\nCache-Control: 
max-age=120\r\nContent-Length: 5\r\n\r\n",
+            "timestamp": "1469733493.993",
+            "body": "hello"
+        }
+        origin.addResponse("sessionlog.json", cache_request, cache_response)
+        origin.addResponse("sessionlog.json", cache_request, cache_response)
+
+        purge_request = {
+            "headers": f"PURGE {self._cache_path} HTTP/1.1\r\nHost: 
cache-probe.test\r\n\r\n",
+            "timestamp": "1469733493.993",
+            "body": ""
+        }
+        purge_response = {
+            "headers": "HTTP/1.1 200 OK\r\nConnection: 
close\r\nContent-Length: 0\r\n\r\n",
+            "timestamp": "1469733493.993",
+            "body": ""
+        }
+        origin.addResponse("sessionlog.json", purge_request, purge_response)
+        return origin
+
+    def _configure_traffic_server(self, tr: 'TestRun') -> 'Process':
+        '''Configure the Traffic Server process.'''
+        ts = tr.MakeATSProcess("ts_cache_dir_probe", enable_cache=True)
+        self._ts = ts
+        ts.Disk.records_config.update(
+            {
+                'proxy.config.diags.debug.enabled': 1,
+                'proxy.config.diags.debug.tags': 'http|cache',
+                'proxy.config.http.cache.required_headers': 0,
+                # Keep ATS running as the invoking user inside sudo (no 
privilege drop).
+                'proxy.config.admin.user_id': '#-1',
+            })
+        ts.Disk.remap_config.AddLine(f'map / 
http://127.0.0.1:{self._origin.Variables.Port}')
+        return ts
+
+    def _configure_bpftrace(self, tr: 'TestRun') -> 'Process':
+        '''Configure the bpftrace process for the cache directory probes.'''
+        bpftrace = tr.Processes.Process('bpftrace')
+        self._bpftrace = bpftrace
+
+        tr.Setup.Copy(self.bt_script)
+        tr_script = os.path.join(tr.RunDirectory, self.bt_script)
+
+        # fan out output so AuTest stream checks still work
+        tee_path = os.path.join(tr.RunDirectory, 'bpftrace.out')
+        bpftrace.Command = f"bpftrace {tr_script}"
+        bpftrace.ReturnCode = 0
+        bpftrace.Streams.All += Testers.ContainsExpression('cache_dir_insert', 
'cache_dir_insert probe fired.')
+        bpftrace.Streams.All += Testers.ContainsExpression('cache_dir_remove', 
'cache_dir_remove probe fired.')
+
+        return bpftrace
+
+    def _configure_client(self, tr: 'TestRun') -> 'Process':
+        '''Configure the client traffic to exercise cache insert and purge.'''
+        client = tr.Processes.Default
+        self._client = client
+        cache_url = 
f"http://127.0.0.1:{self._ts.Variables.port}{self._cache_path}";
+        # Ideally we don't need this "sleep 1", but I haven't been able to get 
it to work with the Ready = When.FileContains(...) approach.
+        client.Command = (
+            f"sleep 1 && curl -sSf -o /dev/null -H 'Host: cache-probe.test' 
{cache_url} && "
+            f"curl -sSf -o /dev/null -X PURGE -H 'Host: cache-probe.test' 
{cache_url} && "
+            f"curl -sSf -o /dev/null -H 'Host: cache-probe.test' {cache_url}")
+        client.ReturnCode = 0
+        client.Env = self._ts.Env
+
+        self._ts.StartBefore(self._origin)  # origin before ts
+        self._bpftrace.StartBefore(self._ts)  # ts before bpftrace
+        client.StartBefore(self._bpftrace)  # bpftrace before client
+        return client
+
+
+CacheDirProbeTest()

Reply via email to