From: Maxime Henrion Date: Tue, 7 Jul 2026 13:10:59 +0000 (-0400) Subject: MINOR: cache: add a counter for cache hits serving early hints X-Git-Url: http://git.kaiwu.me/%7Bsubstring-before(@doc,%20'.xml')%7D.html?a=commitdiff_plain;h=6f8c422252cece0875d4e98e5808376a7686ecf5;p=haproxy.git MINOR: cache: add a counter for cache hits serving early hints Add a cache_hint_hits counter to the per-proxy HTTP stats, incremented each time the cache emits a 103 Early Hints response. It is reported in the CSV/typed outputs, on the stats page next to the cache hits, and exported to prometheus as http_cache_hint_hits_total. Since the new field changes the shm stats file mapping, its minor version is bumped along with the object size assertion. --- diff --git a/addons/promex/README b/addons/promex/README index 4d52c1d1a..9ed37debe 100644 --- a/addons/promex/README +++ b/addons/promex/README @@ -259,6 +259,7 @@ listed below. Metrics from extra counters are not listed. | haproxy_frontend_failed_header_rewriting_total | | haproxy_frontend_http_cache_lookups_total | | haproxy_frontend_http_cache_hits_total | +| haproxy_frontend_http_cache_hint_hits_total | | haproxy_frontend_internal_errors_total | +-------------------------------------------------+ @@ -329,6 +330,7 @@ listed below. Metrics from extra counters are not listed. | haproxy_backend_connection_reuses_total | | haproxy_backend_http_cache_lookups_total | | haproxy_backend_http_cache_hits_total | +| haproxy_backend_http_cache_hint_hits_total | | haproxy_backend_max_queue_time_seconds | | haproxy_backend_max_connect_time_seconds | | haproxy_backend_max_response_time_seconds | diff --git a/addons/promex/service-prometheus.c b/addons/promex/service-prometheus.c index a792fed99..a0ebdff63 100644 --- a/addons/promex/service-prometheus.c +++ b/addons/promex/service-prometheus.c @@ -587,6 +587,7 @@ static int promex_dump_front_metrics(struct appctx *appctx, struct htx *htx) case ST_I_PX_INTERCEPTED: case ST_I_PX_CACHE_LOOKUPS: case ST_I_PX_CACHE_HITS: + case ST_I_PX_CACHE_HINT_HITS: case ST_I_PX_COMP_IN: case ST_I_PX_COMP_OUT: case ST_I_PX_COMP_BYP: @@ -1065,6 +1066,7 @@ static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx) case ST_I_PX_REQ_TOT: case ST_I_PX_CACHE_LOOKUPS: case ST_I_PX_CACHE_HITS: + case ST_I_PX_CACHE_HINT_HITS: case ST_I_PX_COMP_IN: case ST_I_PX_COMP_OUT: case ST_I_PX_COMP_BYP: diff --git a/doc/management.txt b/doc/management.txt index d71288473..19bc71ff3 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -1375,6 +1375,8 @@ Here is the list of static fields using the proxy statistics domain: process started 117. resbout [LFBS]: total number of response bytes sent since the worker process started + 118. cache_hint_hits [.FB.]: cumulative number of cache hits having served + early hints For all other statistics domains, the presence or the order of the fields are not guaranteed. In this case, the header line should always be used to parse diff --git a/include/haproxy/counters-t.h b/include/haproxy/counters-t.h index 301490d29..b606428a8 100644 --- a/include/haproxy/counters-t.h +++ b/include/haproxy/counters-t.h @@ -91,6 +91,7 @@ struct fe_counters_shared_tg { long long cum_req[4]; /* cumulated number of processed other/h1/h2/h3 requests */ long long cache_hits; /* cache hits */ long long cache_lookups;/* cache lookups */ + long long cache_hint_hits; /* cache hits serving early hints */ long long comp_rsp; /* number of compressed responses */ long long rsp[6]; /* http response codes */ } http; @@ -144,6 +145,7 @@ struct be_counters_shared_tg { long long cache_hits; /* cache hits */ long long cache_lookups;/* cache lookups */ + long long cache_hint_hits; /* cache hits serving early hints */ long long comp_rsp; /* number of compressed responses */ long long rsp[6]; /* http response codes */ diff --git a/include/haproxy/stats-file-t.h b/include/haproxy/stats-file-t.h index e730681b4..0343ca9fe 100644 --- a/include/haproxy/stats-file-t.h +++ b/include/haproxy/stats-file-t.h @@ -15,7 +15,7 @@ enum stfile_domain { }; #define SHM_STATS_FILE_VER_MAJOR 1 -#define SHM_STATS_FILE_VER_MINOR 2 +#define SHM_STATS_FILE_VER_MINOR 3 #define SHM_STATS_FILE_HEARTBEAT_TIMEOUT 60 /* passed this delay (seconds) process which has not * sent heartbeat will be considered down diff --git a/include/haproxy/stats-t.h b/include/haproxy/stats-t.h index 1078f90f9..57a38c311 100644 --- a/include/haproxy/stats-t.h +++ b/include/haproxy/stats-t.h @@ -495,6 +495,7 @@ enum stat_idx_px { ST_I_PX_REQ_OUT, ST_I_PX_RES_IN, ST_I_PX_RES_OUT, + ST_I_PX_CACHE_HINT_HITS, /* must always be the last one */ ST_I_PX_MAX }; diff --git a/src/cache.c b/src/cache.c index bc3d3d43a..d5dad45d6 100644 --- a/src/cache.c +++ b/src/cache.c @@ -2442,16 +2442,17 @@ static int should_send_notmodified_response(struct cache *cache, struct htx *htx * length . The format matches what http_action_store_cache * writes: concatenated records of {uint16_t length, char value[length]}, each * value being a Link header value. Skipped for HTTP/1.0 clients. + * Returns 1 if a 103 response was emitted, 0 otherwise. */ -static void cache_emit_early_hints(struct stream *s, const char *hint_data, - unsigned int hint_data_len) +static int cache_emit_early_hints(struct stream *s, const char *hint_data, + unsigned int hint_data_len) { struct htx *htx; const char *p = hint_data; const char *end = hint_data + hint_data_len; if (!(s->txn.http->req.flags & HTTP_MSGF_VER_11)) - return; + return 0; htx = http_early_hint_start(s); if (!htx) @@ -2470,11 +2471,12 @@ static void cache_emit_early_hints(struct stream *s, const char *hint_data, if (!http_early_hint_end(s)) goto error; - return; + return 1; error: channel_htx_truncate(&s->res, htxbuf(&s->res.buf)); s->txn.http->status = 0; + return 0; } enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *px, @@ -2586,8 +2588,20 @@ enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *p shctx_wrunlock(shctx); cache_rdunlock(cache_tree); - if (hint_buf && b_data(hint_buf) > 0) - cache_emit_early_hints(s, b_orig(hint_buf), b_data(hint_buf)); + if (hint_buf && b_data(hint_buf) > 0 && + cache_emit_early_hints(s, b_orig(hint_buf), b_data(hint_buf))) { + long long *ctr = NULL; + + if (px == strm_fe(s)) { + if (px->fe_counters.shared.tg) + ctr = &px->fe_counters.shared.tg[tgid - 1]->p.http.cache_hint_hits; + } + else if (px->be_counters.shared.tg) + ctr = &px->be_counters.shared.tg[tgid - 1]->p.http.cache_hint_hits; + + if (ctr) + _HA_ATOMIC_INC(ctr); + } /* In case of Vary, we could have multiple entries with the same * primary hash. We need to calculate the secondary hash in order diff --git a/src/stats-file.c b/src/stats-file.c index 205b886b1..7988f3bb9 100644 --- a/src/stats-file.c +++ b/src/stats-file.c @@ -849,7 +849,7 @@ int shm_stats_file_prepare(void) BUG_ON(sizeof(struct shm_stats_file_hdr) != 672, "shm_stats_file_hdr struct size changed, " "it is part of the exported API: ensure all precautions were taken (ie: shm_stats_file " "version change) before adjusting this"); - BUG_ON(sizeof(struct shm_stats_file_object) != 552, "shm_stats_file_object struct size changed, " + BUG_ON(sizeof(struct shm_stats_file_object) != 560, "shm_stats_file_object struct size changed, " "it is part of the exported API: ensure all precautions were taken (ie: shm_stats_file " "version change) before adjusting this"); diff --git a/src/stats-html.c b/src/stats-html.c index 469ca6ac1..4deeb695a 100644 --- a/src/stats-html.c +++ b/src/stats-html.c @@ -611,6 +611,7 @@ int stats_dump_fields_html(struct buffer *out, "Intercepted requests:%s" "Cache lookups:%s" "Cache hits:%s(%d%%)" + "Cache hint hits:%s" "Failed hdr rewrites:%s" "Internal errors:%s" "", @@ -619,6 +620,7 @@ int stats_dump_fields_html(struct buffer *out, U2H(stats[ST_I_PX_CACHE_HITS].u.u64), stats[ST_I_PX_CACHE_LOOKUPS].u.u64 ? (int)(100 * stats[ST_I_PX_CACHE_HITS].u.u64 / stats[ST_I_PX_CACHE_LOOKUPS].u.u64) : 0, + U2H(stats[ST_I_PX_CACHE_HINT_HITS].u.u64), U2H(stats[ST_I_PX_WREW].u.u64), U2H(stats[ST_I_PX_EINT].u.u64)); } @@ -1216,6 +1218,7 @@ int stats_dump_fields_html(struct buffer *out, "- other responses:%s" "Cache lookups:%s" "Cache hits:%s(%d%%)" + "Cache hint hits:%s" "Failed hdr rewrites:%s" "Internal errors:%s" "", @@ -1237,6 +1240,7 @@ int stats_dump_fields_html(struct buffer *out, U2H(stats[ST_I_PX_CACHE_HITS].u.u64), stats[ST_I_PX_CACHE_LOOKUPS].u.u64 ? (int)(100 * stats[ST_I_PX_CACHE_HITS].u.u64 / stats[ST_I_PX_CACHE_LOOKUPS].u.u64) : 0, + U2H(stats[ST_I_PX_CACHE_HINT_HITS].u.u64), U2H(stats[ST_I_PX_WREW].u.u64), U2H(stats[ST_I_PX_EINT].u.u64)); } diff --git a/src/stats-proxy.c b/src/stats-proxy.c index de924bec1..9238810ef 100644 --- a/src/stats-proxy.c +++ b/src/stats-proxy.c @@ -196,6 +196,7 @@ const struct stat_col stat_cols_px[ST_I_PX_MAX] = { [ST_I_PX_REQ_OUT] = ME_NEW_PX_SHARED("reqbout", "req_bytes_out_total", FN_COUNTER, FF_U64, req_out, STATS_PX_CAP_LFBS, "Total number of request bytes sent since process started"), [ST_I_PX_RES_IN] = ME_NEW_PX_SHARED("resbin", "res_bytes_in_total", FN_COUNTER, FF_U64, res_in, STATS_PX_CAP_LFBS, "Total number of response bytes received since process started"), [ST_I_PX_RES_OUT] = ME_NEW_PX_SHARED("resbout", "res_bytes_out_total", FN_COUNTER, FF_U64, res_out, STATS_PX_CAP_LFBS, "Total number of response bytes sent since process started"), + [ST_I_PX_CACHE_HINT_HITS] = ME_NEW_PX_SHARED("cache_hint_hits", "http_cache_hint_hits_total", FN_COUNTER, FF_U64, p.http.cache_hint_hits, STATS_PX_CAP__FB_, "Total number of HTTP requests for which a 103 Early Hints response was emitted from the cache on this frontend/backend since the worker process started"), }; @@ -236,6 +237,7 @@ static int stcol_hide(enum stat_idx_px idx, enum obj_type *objt) case ST_I_PX_INTERCEPTED: case ST_I_PX_CACHE_LOOKUPS: case ST_I_PX_CACHE_HITS: + case ST_I_PX_CACHE_HINT_HITS: return px->mode != PR_MODE_HTTP; case ST_I_PX_CHKFAIL: