commit 9f52a516e99652b7b174fd71cf6cf6bdf7bf9614
Author: David A. Holland <dholland+wt@sauclovia.org>
Date:   Fri Jun 24 19:47:15 2022 -0400

    Tidy up the way the names of join statistics are handled.
    
    Generate the statistics names with a "join:" prefix and a "%%" where
    the table name goes. Among other things, this fixes the way the docs
    come out. It also makes the names in stat.c not appear to be broken
    and generally makes the circumstances clearer.
    
    For now at least, the code inside the library that builds the
    user-visible names on the fly knows where the "%%" is (and asserts
    about it), so that code changes only slightly. (It could be made more
    general, but that probably isn't worthwhile.)

diff --git a/dist/stat.py b/dist/stat.py
index 73f8ced77..155579565 100644
--- a/dist/stat.py
+++ b/dist/stat.py
@@ -96,6 +96,8 @@ def print_defines():
 /*!
  * @}
  * @name Statistics for join cursors
+ * The \\c %% is replaced with the URI of an object being joined, such as
+ * \\c index:mytable:some_index.
  * @anchor statistics_join
  * @{
  */
diff --git a/dist/stat_data.py b/dist/stat_data.py
index 35211191a..e9765d915 100644
--- a/dist/stat_data.py
+++ b/dist/stat_data.py
@@ -77,9 +77,10 @@ class DhandleStat(Stat):
     def __init__(self, name, desc, flags=''):
         Stat.__init__(self, name, DhandleStat.prefix, desc, flags)
 class JoinStat(Stat):
-    prefix = ''  # prefix is inserted dynamically
+    # The %% is where the name of the join gets inserted into the description at runtime.
+    prefix = 'join'
     def __init__(self, name, desc, flags=''):
-        Stat.__init__(self, name, JoinStat.prefix, desc, flags)
+        Stat.__init__(self, name, JoinStat.prefix, '%%: ' + desc, flags)
 class LockStat(Stat):
     prefix = 'lock'
     def __init__(self, name, desc, flags=''):
diff --git a/src/cursor/cur_stat.c b/src/cursor/cur_stat.c
index 416c73d34..0cee1df6e 100644
--- a/src/cursor/cur_stat.c
+++ b/src/cursor/cur_stat.c
@@ -483,15 +483,25 @@ __curstat_join_desc(WT_CURSOR_STAT *cst, int slot, const char **resultp)
 {
     WT_JOIN_STATS_GROUP *sgrp;
     WT_SESSION_IMPL *session;
-    size_t len;
+    size_t len, prefixlen;
     const char *static_desc;
 
     sgrp = &cst->u.join_stats_group;
     session = CUR2S(sgrp->join_cursor);
+    prefixlen = strlen("join: ");
+
+    /*
+     * The static description contains %% where the join description should be inserted. Since this
+     * is always after the category name, we don't have to search. Assert that it's where we expect
+     * in case someone alters the stats code generator without visiting this function.
+     */
     WT_RET(__wt_stat_join_desc(cst, slot, &static_desc));
-    len = strlen("join: ") + strlen(sgrp->desc_prefix) + strlen(static_desc) + 1;
+    WT_ASSERT(session, static_desc[prefixlen] == '%' && static_desc[prefixlen + 1] == '%');
+
+    len = strlen(sgrp->desc_prefix) + strlen(static_desc) - 2 + 1;
     WT_RET(__wt_realloc(session, NULL, len, &cst->desc_buf));
-    WT_RET(__wt_snprintf(cst->desc_buf, len, "join: %s%s", sgrp->desc_prefix, static_desc));
+    WT_RET(__wt_snprintf(cst->desc_buf, len, "%.*s%s%s", (int)prefixlen, static_desc,
+      sgrp->desc_prefix, static_desc + prefixlen + 2));
     *resultp = cst->desc_buf;
     return (0);
 }
diff --git a/src/include/wiredtiger.in b/src/include/wiredtiger.in
index 4d4468077..0380e2c2c 100644
--- a/src/include/wiredtiger.in
+++ b/src/include/wiredtiger.in
@@ -7264,18 +7264,20 @@ extern int wiredtiger_extension_terminate(WT_CONNECTION *connection);
 /*!
  * @}
  * @name Statistics for join cursors
+ * The \c %% is replaced with the URI of an object being joined, such as
+ * \c index:mytable:some_index.
  * @anchor statistics_join
  * @{
  */
-/*! : accesses to the main table */
+/*! join: %%: accesses to the main table */
 #define	WT_STAT_JOIN_MAIN_ACCESS			3000
-/*! : bloom filter false positives */
+/*! join: %%: bloom filter false positives */
 #define	WT_STAT_JOIN_BLOOM_FALSE_POSITIVE		3001
-/*! : checks that conditions of membership are satisfied */
+/*! join: %%: checks that conditions of membership are satisfied */
 #define	WT_STAT_JOIN_MEMBERSHIP_CHECK			3002
-/*! : items inserted into a bloom filter */
+/*! join: %%: items inserted into a bloom filter */
 #define	WT_STAT_JOIN_BLOOM_INSERT			3003
-/*! : items iterated */
+/*! join: %%: items iterated */
 #define	WT_STAT_JOIN_ITERATED				3004
 
 /*!
diff --git a/src/support/stat.c b/src/support/stat.c
index eb64c3d92..8b7d6f12b 100644
--- a/src/support/stat.c
+++ b/src/support/stat.c
@@ -2896,11 +2896,11 @@ __wt_stat_connection_aggregate(WT_CONNECTION_STATS **from, WT_CONNECTION_STATS *
 }
 
 static const char *const __stats_join_desc[] = {
-  ": accesses to the main table",
-  ": bloom filter false positives",
-  ": checks that conditions of membership are satisfied",
-  ": items inserted into a bloom filter",
-  ": items iterated",
+  "join: %%: accesses to the main table",
+  "join: %%: bloom filter false positives",
+  "join: %%: checks that conditions of membership are satisfied",
+  "join: %%: items inserted into a bloom filter",
+  "join: %%: items iterated",
 };
 
 int
