[PATCH v2 05/15] ceph: add BLOG logger core
Alex Markuze <[email protected]> Mon, 6 Jul 2026 14:38:33 +0000
| Newsgroups | org.kernel.vger.ceph-devel |
|---|---|
| Message-ID | <[email protected]> |
Add blog_core.c: central logger, source-ID registry with per-callsite caching (smp_store_release/smp_load_acquire plus generation counter), circular entry buffer, and iteration API for debugfs consumers. Signed-off-by: Alex Markuze <[email protected]> --- fs/ceph/blog_core.c | 424 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 fs/ceph/blog_core.c diff --git a/fs/ceph/blog_core.c b/fs/ceph/blog_core.c new file mode 100644 index 000000000000..da8567c53229 --- /dev/null +++ b/fs/ceph/blog_core.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Binary Logging Infrastructure - Core Implementation + * + * Per-module logger instances, source-ID registration, log-entry + * reservation/commit, and pagefrag-based context management. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/printk.h> +#include <linux/time.h> +#include <linux/percpu.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/atomic.h> + +#include <linux/rhashtable.h> +#include <linux/ceph/blog.h> +#include <linux/ceph/blog_batch.h> +#include <linux/ceph/blog_pagefrag.h> +#include <linux/ceph/blog_ser.h> +#include <linux/ceph/blog_des.h> +#include <linux/ceph/blog_module.h> + +/* Core BLOG functions - all require a valid logger parameter */ + +static struct blog_tls_ctx *lookup_active_ctx(struct blog_logger *logger) +{ + struct task_struct *task = current; + struct blog_task_entry *entry; + + if (!logger) + return NULL; + + rcu_read_lock(); + entry = rhashtable_lookup_fast(&logger->task_map, &task, + logger->task_map.p); + if (entry && entry->pid == task->pid) { + struct blog_tls_ctx *ctx = entry->ctx; + + rcu_read_unlock(); + return ctx; + } + rcu_read_unlock(); + return NULL; +} + +/** + * blog_get_tls_ctx - Get or create TLS context for current task + * @logger: Logger instance to use + * + * Returns pointer to TLS context or NULL on error + */ +struct blog_tls_ctx *blog_get_tls_ctx(struct blog_logger *logger) +{ + struct blog_tls_ctx *ctx; + + if (!logger) + return NULL; + + ctx = lookup_active_ctx(logger); + if (ctx) + return ctx; + + /* Delegate to module layer which manages the rhashtable insert */ + if (logger->owner_ctx) + return blog_get_tls_ctx_ctx(logger->owner_ctx); + + return NULL; +} + +static bool blog_source_matches(const struct blog_source_info *info, + const char *file, const char *func, + unsigned int line, const char *fmt) +{ + return info->file && info->func && info->fmt && + info->line == line && info->fmt == fmt && + !strcmp(info->file, file) && !strcmp(info->func, func); +} + +/** + * blog_get_source_id - Get or create a source ID for the given location + * @logger: Logger instance to use (NULL for global) + * @file: Source file name + * @func: Function name + * @line: Line number + * @fmt: Format string + * + * Returns a unique ID for this source location + */ +u32 blog_get_source_id(struct blog_logger *logger, const char *file, + const char *func, unsigned int line, const char *fmt) +{ + struct blog_source_info *info; + u32 id, next_id; + + if (!logger) + return 0; + + spin_lock(&logger->source_lock); + next_id = atomic_read(&logger->next_source_id); + for (id = 1; id < next_id; id++) { + info = &logger->source_map[id]; + if (blog_source_matches(info, file, func, line, fmt)) + goto out_unlock; + } + + id = next_id; + if (id >= BLOG_MAX_SOURCE_IDS) { + atomic_set(&logger->next_source_id, BLOG_MAX_SOURCE_IDS); + spin_unlock(&logger->source_lock); + pr_warn_once("blog: source ID overflow\n"); + return 0; + } + + atomic_inc(&logger->next_source_id); + info = &logger->source_map[id]; + info->file = file; + info->func = func; + info->line = line; + info->fmt = fmt; + info->warn_count = 0; +#if BLOG_TRACK_USAGE + atomic_set(&info->task_usage, 0); + atomic_set(&info->task_bytes, 0); +#endif + +out_unlock: + spin_unlock(&logger->source_lock); + return id; +} + +u32 blog_get_source_id_cached(struct blog_logger *logger, + struct blog_source_id_cache *cache, + const char *file, const char *func, + unsigned int line, const char *fmt) +{ + u32 sid; + + if (!logger) + return 0; + if (cache) { + sid = smp_load_acquire(&cache->id); + if (sid && READ_ONCE(cache->logger) == logger && + READ_ONCE(cache->generation) == logger->generation) + return sid; + } + + sid = blog_get_source_id(logger, file, func, line, fmt); + if (cache && sid) { + WRITE_ONCE(cache->logger, logger); + WRITE_ONCE(cache->generation, logger->generation); + smp_store_release(&cache->id, sid); + } + + return sid; +} + +/** + * blog_get_source_info - Get source info for a given ID + * @id: Source ID + * + * Returns the source information for this ID + */ +struct blog_source_info *blog_get_source_info(struct blog_logger *logger, u32 id) +{ + if (!logger || unlikely(id == 0 || id >= BLOG_MAX_SOURCE_IDS)) + return NULL; + return &logger->source_map[id]; +} + +/** + * blog_log - Reserve buffer for a binary log message + * @logger: Logger instance to use + * @source_id: Source ID for this location + * @client_id: Client ID for this message (module-specific) + * @needed_size: Size needed for the message + * + * Only one reservation may be outstanding per context at a time. + * The caller must call blog_log_commit() before issuing another + * blog_log() on the same context. + * + * Returns a buffer to write the message into, or NULL on failure + */ +void *blog_log(struct blog_logger *logger, u32 source_id, u8 client_id, size_t needed_size) +{ + struct blog_tls_ctx *ctx; + struct blog_log_entry *entry = NULL; + int alloc; + int retry_count = 0; + +#if BLOG_TRACK_USAGE + struct blog_source_info *source; +#endif + /* Preserve payload length; compute rounded total allocation separately */ + size_t payload_len = needed_size; + + if (payload_len > BLOG_MAX_PAYLOAD) { + pr_warn_once("%s: payload %zu exceeds max %u\n", + __func__, payload_len, BLOG_MAX_PAYLOAD); + return NULL; + } + + needed_size = round_up(payload_len + sizeof(struct blog_log_entry), 8); +#if BLOG_TRACK_USAGE + source = blog_get_source_info(logger, source_id); + if (unlikely(source)) { + atomic_inc(&source->task_usage); + atomic_add(needed_size, &source->task_bytes); + } +#endif + + while (entry == NULL) { + struct blog_pagefrag *pf; + + ctx = blog_get_ctx(logger); + if (!ctx) + return NULL; + if (unlikely(retry_count)) { + pr_debug( + "[%d]Retrying allocation with ctx %llu (%s, pid %d) (retry %d, needed_size=%zu @ %d)\n", + smp_processor_id(), ctx->id, ctx->comm, + ctx->pid, retry_count, needed_size, source_id); + } + + pf = blog_ctx_pf(ctx); + + if (test_and_clear_bit(BLOG_CTX_NEEDS_RESET, &ctx->flags)) { + blog_pagefrag_reset(pf); + ctx->pending_offset = 0; + ctx->pending_size = 0; + } + + alloc = blog_pagefrag_reserve(pf, needed_size); + if (alloc == -ENOMEM) { + pr_debug("%s: allocation failed (needed %zu), resetting context\n", + __func__, needed_size); + blog_pagefrag_reset(pf); + retry_count++; + if (retry_count > 3) { + pr_err("%s: failed to allocate after 3 retries\n", __func__); + return NULL; + } + continue; + } + + entry = blog_pagefrag_get_ptr(pf, alloc); + if (!entry) { + pr_err("%s: failed to get pointer from pagefrag\n", __func__); + return NULL; + } + pf->last_entry = entry; + + /* Store pending publish info for blog_log_commit() */ + if (WARN_ON_ONCE(ctx->pending_size != 0)) + return NULL; + ctx->pending_offset = alloc; + ctx->pending_size = needed_size; + } + +#if BLOG_DEBUG_POISON + entry->debug_poison = BLOG_LOG_ENTRY_POISON; +#endif + entry->ts_delta = (u32)(jiffies - ctx->base_jiffies); + entry->source_id = (u16)source_id; + entry->len = (u16)payload_len; + entry->client_id = client_id; + entry->flags = 0; + + return entry->buffer; +} + +/** + * blog_log_commit - Publish a reserved log entry + * @logger: Logger instance + * @actual_size: Actual bytes written during serialization + * + * Publishes the log entry that was reserved by the last blog_log() call. + * Must be called after serialization is complete to make the entry visible + * to readers. + * + * Context: Same context as the preceding blog_log() call + * Return: 0 on success, negative error code on failure + */ +int blog_log_commit(struct blog_logger *logger, size_t actual_size) +{ + struct blog_tls_ctx *ctx = blog_get_ctx(logger); + struct blog_pagefrag *pf; + struct blog_log_entry *entry; + size_t total_size; + + if (!ctx) + return -EINVAL; + + /* Validate that actual_size fits within the reservation */ + total_size = round_up(sizeof(struct blog_log_entry) + actual_size, 8); + if (total_size > ctx->pending_size) + return -ENOSPC; + + pf = blog_ctx_pf(ctx); + + entry = blog_pagefrag_get_ptr(pf, ctx->pending_offset); + if (!entry) { + ctx->pending_offset = 0; + ctx->pending_size = 0; + return -EFAULT; + } + entry->len = (u16)actual_size; + + blog_pagefrag_publish(pf, ctx->pending_offset + total_size); + ctx->pending_offset = 0; + ctx->pending_size = 0; + + return 0; +} + +/** + * blog_get_ctx - Get logging context for current task + */ +struct blog_tls_ctx *blog_get_ctx(struct blog_logger *logger) +{ + return blog_get_tls_ctx(logger); +} + +/** + * blog_log_iter_init - Initialize the iterator for a specific pagefrag + */ +void blog_log_iter_init(struct blog_log_iter *iter, struct blog_pagefrag *pf, + u64 head_snapshot) +{ + if (!iter || !pf) + return; + + iter->pf = pf; + iter->current_offset = 0; + iter->end_offset = head_snapshot; + iter->prev_offset = 0; + iter->steps = 0; +} + +/** + * blog_log_iter_next - Get next log entry + */ +struct blog_log_entry *blog_log_iter_next(struct blog_log_iter *iter) +{ + struct blog_log_entry *entry; + + if (!iter || iter->current_offset >= iter->end_offset) + return NULL; + + /* Ensure the entry header itself fits within the snapshot. */ + if (iter->current_offset + sizeof(struct blog_log_entry) > + iter->end_offset) + return NULL; + + entry = blog_pagefrag_get_ptr(iter->pf, iter->current_offset); + if (!entry) + return NULL; + + iter->prev_offset = iter->current_offset; + iter->current_offset += + round_up(sizeof(struct blog_log_entry) + entry->len, 8); + iter->steps++; + + /* + * Clamp to the snapshot boundary: a corrupted entry->len could + * push current_offset past end_offset into garbage memory. + */ + if (iter->current_offset > iter->end_offset) + iter->current_offset = iter->end_offset; + + return entry; +} + +/** + * blog_des_entry - Deserialize entry with callback + */ +int blog_des_entry(struct blog_logger *logger, struct blog_log_entry *entry, + char *output, size_t out_size, blog_client_des_fn client_cb) +{ + int len = 0; + struct blog_source_info *source; + + if (!entry || !output) + return -EINVAL; + + /* Let module handle client_id if callback provided */ + if (client_cb) { + len = client_cb(output, out_size, entry->client_id); + if (len < 0) + return len; + } + + /* Get source info */ + source = blog_get_source_info(logger, entry->source_id); + if (!source) { + len += snprintf(output + len, out_size - len, + "[unknown source %u]", entry->source_id); + return len; + } + + /* Add source location */ + len += snprintf(output + len, out_size - len, "[%s:%s:%u] ", + source->file, source->func, source->line); + + /* Deserialize the buffer content */ + { + int ret; + + ret = blog_des_reconstruct(source->fmt, entry->buffer, + entry->len, output + len, + out_size - len); + if (ret < 0) + return ret; + len += ret; + } + + return len; +} -- 2.34.1