Skip to content

Commit b1137e0

Browse files
Alex Markuzeidryomov
authored andcommitted
ceph: add subvolume metrics collection and reporting
Add complete infrastructure for per-subvolume I/O metrics collection and reporting to the MDS. This enables administrators to monitor I/O patterns at the subvolume granularity, which is useful for multi-tenant CephFS deployments. This patch adds: - CEPHFS_FEATURE_SUBVOLUME_METRICS feature flag for MDS negotiation - CEPH_SUBVOLUME_ID_NONE constant (0) for unknown/unset state - Red-black tree based metrics tracker for efficient per-subvolume aggregation with kmem_cache for entry allocations - Wire format encoding matching the MDS C++ AggregatedIOMetrics struct - Integration with the existing CLIENT_METRICS message - Recording of I/O operations from file read/write and writeback paths - Debugfs interfaces for monitoring (metrics/subvolumes, metrics/metric_features) Metrics tracked per subvolume include: - Read/write operation counts - Read/write byte counts - Read/write latency sums (for average calculation) The metrics are periodically sent to the MDS as part of the existing metrics reporting infrastructure when the MDS advertises support for the SUBVOLUME_METRICS feature. CEPH_SUBVOLUME_ID_NONE enforces subvolume_id immutability. Following the FUSE client convention, 0 means unknown/unset. Once an inode has a valid (non-zero) subvolume_id, it should not change during the inode's lifetime. Signed-off-by: Alex Markuze <amarkuze@redhat.com> Reviewed-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
1 parent 4a1c543 commit b1137e0

12 files changed

Lines changed: 1018 additions & 14 deletions

File tree

fs/ceph/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ obj-$(CONFIG_CEPH_FS) += ceph.o
88
ceph-y := super.o inode.o dir.o file.o locks.o addr.o ioctl.o \
99
export.o caps.o snap.o xattr.o quota.o io.o \
1010
mds_client.o mdsmap.o strings.o ceph_frag.o \
11-
debugfs.o util.o metric.o
11+
debugfs.o util.o metric.o subvolume_metrics.o
1212

1313
ceph-$(CONFIG_CEPH_FSCACHE) += cache.o
1414
ceph-$(CONFIG_CEPH_FS_POSIX_ACL) += acl.o

fs/ceph/addr.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "mds_client.h"
2020
#include "cache.h"
2121
#include "metric.h"
22+
#include "subvolume_metrics.h"
2223
#include "crypto.h"
2324
#include <linux/ceph/osd_client.h>
2425
#include <linux/ceph/striper.h>
@@ -259,6 +260,10 @@ static void finish_netfs_read(struct ceph_osd_request *req)
259260
osd_data->length), false);
260261
}
261262
if (err > 0) {
263+
ceph_subvolume_metrics_record_io(fsc->mdsc, ceph_inode(inode),
264+
false, err,
265+
req->r_start_latency,
266+
req->r_end_latency);
262267
subreq->transferred = err;
263268
err = 0;
264269
}
@@ -823,6 +828,10 @@ static int write_folio_nounlock(struct folio *folio,
823828

824829
ceph_update_write_metrics(&fsc->mdsc->metric, req->r_start_latency,
825830
req->r_end_latency, len, err);
831+
if (err >= 0 && len > 0)
832+
ceph_subvolume_metrics_record_io(fsc->mdsc, ci, true, len,
833+
req->r_start_latency,
834+
req->r_end_latency);
826835
fscrypt_free_bounce_page(bounce_page);
827836
ceph_osdc_put_request(req);
828837
if (err == 0)
@@ -963,6 +972,11 @@ static void writepages_finish(struct ceph_osd_request *req)
963972
ceph_update_write_metrics(&fsc->mdsc->metric, req->r_start_latency,
964973
req->r_end_latency, len, rc);
965974

975+
if (rc >= 0 && len > 0)
976+
ceph_subvolume_metrics_record_io(mdsc, ci, true, len,
977+
req->r_start_latency,
978+
req->r_end_latency);
979+
966980
ceph_put_wrbuffer_cap_refs(ci, total_pages, snapc);
967981

968982
osd_data = osd_req_op_extent_osd_data(req, 0);

fs/ceph/debugfs.c

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,50 @@
99
#include <linux/seq_file.h>
1010
#include <linux/math64.h>
1111
#include <linux/ktime.h>
12+
#include <linux/atomic.h>
1213

1314
#include <linux/ceph/libceph.h>
1415
#include <linux/ceph/mon_client.h>
1516
#include <linux/ceph/auth.h>
1617
#include <linux/ceph/debugfs.h>
18+
#include <linux/ceph/decode.h>
1719

1820
#include "super.h"
1921

2022
#ifdef CONFIG_DEBUG_FS
2123

2224
#include "mds_client.h"
2325
#include "metric.h"
26+
#include "subvolume_metrics.h"
27+
28+
/**
29+
* struct ceph_session_feature_desc - Maps feature bits to names for debugfs
30+
* @bit: Feature bit number from enum ceph_feature_type (see mds_client.h)
31+
* @name: Human-readable feature name for debugfs output
32+
*
33+
* Used by metric_features_show() to display negotiated session features.
34+
*/
35+
struct ceph_session_feature_desc {
36+
unsigned int bit;
37+
const char *name;
38+
};
39+
40+
static const struct ceph_session_feature_desc ceph_session_feature_table[] = {
41+
{ CEPHFS_FEATURE_METRIC_COLLECT, "METRIC_COLLECT" },
42+
{ CEPHFS_FEATURE_REPLY_ENCODING, "REPLY_ENCODING" },
43+
{ CEPHFS_FEATURE_RECLAIM_CLIENT, "RECLAIM_CLIENT" },
44+
{ CEPHFS_FEATURE_LAZY_CAP_WANTED, "LAZY_CAP_WANTED" },
45+
{ CEPHFS_FEATURE_MULTI_RECONNECT, "MULTI_RECONNECT" },
46+
{ CEPHFS_FEATURE_DELEG_INO, "DELEG_INO" },
47+
{ CEPHFS_FEATURE_ALTERNATE_NAME, "ALTERNATE_NAME" },
48+
{ CEPHFS_FEATURE_NOTIFY_SESSION_STATE, "NOTIFY_SESSION_STATE" },
49+
{ CEPHFS_FEATURE_OP_GETVXATTR, "OP_GETVXATTR" },
50+
{ CEPHFS_FEATURE_32BITS_RETRY_FWD, "32BITS_RETRY_FWD" },
51+
{ CEPHFS_FEATURE_NEW_SNAPREALM_INFO, "NEW_SNAPREALM_INFO" },
52+
{ CEPHFS_FEATURE_HAS_OWNER_UIDGID, "HAS_OWNER_UIDGID" },
53+
{ CEPHFS_FEATURE_MDS_AUTH_CAPS_CHECK, "MDS_AUTH_CAPS_CHECK" },
54+
{ CEPHFS_FEATURE_SUBVOLUME_METRICS, "SUBVOLUME_METRICS" },
55+
};
2456

2557
static int mdsmap_show(struct seq_file *s, void *p)
2658
{
@@ -360,6 +392,59 @@ static int status_show(struct seq_file *s, void *p)
360392
return 0;
361393
}
362394

395+
static int subvolume_metrics_show(struct seq_file *s, void *p)
396+
{
397+
struct ceph_fs_client *fsc = s->private;
398+
struct ceph_mds_client *mdsc = fsc->mdsc;
399+
struct ceph_subvol_metric_snapshot *snapshot = NULL;
400+
u32 nr = 0;
401+
u64 total_sent = 0;
402+
u64 nonzero_sends = 0;
403+
u32 i;
404+
405+
if (!mdsc) {
406+
seq_puts(s, "mds client unavailable\n");
407+
return 0;
408+
}
409+
410+
mutex_lock(&mdsc->subvol_metrics_last_mutex);
411+
if (mdsc->subvol_metrics_last && mdsc->subvol_metrics_last_nr) {
412+
nr = mdsc->subvol_metrics_last_nr;
413+
snapshot = kmemdup_array(mdsc->subvol_metrics_last, nr,
414+
sizeof(*snapshot), GFP_KERNEL);
415+
if (!snapshot)
416+
nr = 0;
417+
}
418+
total_sent = mdsc->subvol_metrics_sent;
419+
nonzero_sends = mdsc->subvol_metrics_nonzero_sends;
420+
mutex_unlock(&mdsc->subvol_metrics_last_mutex);
421+
422+
seq_puts(s, "Last sent subvolume metrics:\n");
423+
if (!nr) {
424+
seq_puts(s, " (none)\n");
425+
} else {
426+
seq_puts(s, " subvol_id rd_ops wr_ops rd_bytes wr_bytes rd_lat_us wr_lat_us\n");
427+
for (i = 0; i < nr; i++) {
428+
const struct ceph_subvol_metric_snapshot *e = &snapshot[i];
429+
430+
seq_printf(s, " %-18llu %-9llu %-9llu %-14llu %-14llu %-14llu %-14llu\n",
431+
e->subvolume_id,
432+
e->read_ops, e->write_ops,
433+
e->read_bytes, e->write_bytes,
434+
e->read_latency_us, e->write_latency_us);
435+
}
436+
}
437+
kfree(snapshot);
438+
439+
seq_puts(s, "\nStatistics:\n");
440+
seq_printf(s, " entries_sent: %llu\n", total_sent);
441+
seq_printf(s, " non_zero_sends: %llu\n", nonzero_sends);
442+
443+
seq_puts(s, "\nPending (unsent) subvolume metrics:\n");
444+
ceph_subvolume_metrics_dump(&mdsc->subvol_metrics, s);
445+
return 0;
446+
}
447+
363448
DEFINE_SHOW_ATTRIBUTE(mdsmap);
364449
DEFINE_SHOW_ATTRIBUTE(mdsc);
365450
DEFINE_SHOW_ATTRIBUTE(caps);
@@ -369,7 +454,72 @@ DEFINE_SHOW_ATTRIBUTE(metrics_file);
369454
DEFINE_SHOW_ATTRIBUTE(metrics_latency);
370455
DEFINE_SHOW_ATTRIBUTE(metrics_size);
371456
DEFINE_SHOW_ATTRIBUTE(metrics_caps);
457+
DEFINE_SHOW_ATTRIBUTE(subvolume_metrics);
458+
459+
static int metric_features_show(struct seq_file *s, void *p)
460+
{
461+
struct ceph_fs_client *fsc = s->private;
462+
struct ceph_mds_client *mdsc = fsc->mdsc;
463+
unsigned long session_features = 0;
464+
bool have_session = false;
465+
bool metric_collect = false;
466+
bool subvol_support = false;
467+
bool metrics_enabled = false;
468+
bool subvol_enabled = false;
469+
int i;
470+
471+
if (!mdsc) {
472+
seq_puts(s, "mds client unavailable\n");
473+
return 0;
474+
}
475+
476+
mutex_lock(&mdsc->mutex);
477+
if (mdsc->metric.session) {
478+
have_session = true;
479+
session_features = mdsc->metric.session->s_features;
480+
}
481+
mutex_unlock(&mdsc->mutex);
482+
483+
if (have_session) {
484+
metric_collect =
485+
test_bit(CEPHFS_FEATURE_METRIC_COLLECT,
486+
&session_features);
487+
subvol_support =
488+
test_bit(CEPHFS_FEATURE_SUBVOLUME_METRICS,
489+
&session_features);
490+
}
491+
492+
metrics_enabled = !disable_send_metrics && have_session && metric_collect;
493+
subvol_enabled = metrics_enabled && subvol_support;
494+
495+
seq_printf(s,
496+
"metrics_enabled: %s (disable_send_metrics=%d, session=%s, metric_collect=%s)\n",
497+
metrics_enabled ? "yes" : "no",
498+
disable_send_metrics ? 1 : 0,
499+
have_session ? "yes" : "no",
500+
metric_collect ? "yes" : "no");
501+
seq_printf(s, "subvolume_metrics_enabled: %s\n",
502+
subvol_enabled ? "yes" : "no");
503+
seq_printf(s, "session_feature_bits: 0x%lx\n", session_features);
504+
505+
if (!have_session) {
506+
seq_puts(s, "(no active MDS session for metrics)\n");
507+
return 0;
508+
}
509+
510+
for (i = 0; i < ARRAY_SIZE(ceph_session_feature_table); i++) {
511+
const struct ceph_session_feature_desc *desc =
512+
&ceph_session_feature_table[i];
513+
bool set = test_bit(desc->bit, &session_features);
514+
515+
seq_printf(s, " %-24s : %s\n", desc->name,
516+
set ? "yes" : "no");
517+
}
518+
519+
return 0;
520+
}
372521

522+
DEFINE_SHOW_ATTRIBUTE(metric_features);
373523

374524
/*
375525
* debugfs
@@ -404,6 +554,7 @@ void ceph_fs_debugfs_cleanup(struct ceph_fs_client *fsc)
404554
debugfs_remove(fsc->debugfs_caps);
405555
debugfs_remove(fsc->debugfs_status);
406556
debugfs_remove(fsc->debugfs_mdsc);
557+
debugfs_remove(fsc->debugfs_subvolume_metrics);
407558
debugfs_remove_recursive(fsc->debugfs_metrics_dir);
408559
doutc(fsc->client, "done\n");
409560
}
@@ -468,6 +619,12 @@ void ceph_fs_debugfs_init(struct ceph_fs_client *fsc)
468619
&metrics_size_fops);
469620
debugfs_create_file("caps", 0400, fsc->debugfs_metrics_dir, fsc,
470621
&metrics_caps_fops);
622+
debugfs_create_file("metric_features", 0400, fsc->debugfs_metrics_dir,
623+
fsc, &metric_features_fops);
624+
fsc->debugfs_subvolume_metrics =
625+
debugfs_create_file("subvolumes", 0400,
626+
fsc->debugfs_metrics_dir, fsc,
627+
&subvolume_metrics_fops);
471628
doutc(fsc->client, "done\n");
472629
}
473630

fs/ceph/file.c

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@
1919
#include "cache.h"
2020
#include "io.h"
2121
#include "metric.h"
22+
#include "subvolume_metrics.h"
23+
24+
/*
25+
* Record I/O for subvolume metrics tracking.
26+
*
27+
* Callers must ensure bytes > 0 for reads (ret > 0 check) to avoid counting
28+
* EOF as an I/O operation. For writes, the condition is (ret >= 0 && len > 0).
29+
*/
30+
static inline void ceph_record_subvolume_io(struct inode *inode, bool is_write,
31+
ktime_t start, ktime_t end,
32+
size_t bytes)
33+
{
34+
if (!bytes)
35+
return;
36+
37+
ceph_subvolume_metrics_record_io(ceph_sb_to_mdsc(inode->i_sb),
38+
ceph_inode(inode),
39+
is_write, bytes, start, end);
40+
}
2241

2342
static __le32 ceph_flags_sys2wire(struct ceph_mds_client *mdsc, u32 flags)
2443
{
@@ -1140,6 +1159,15 @@ ssize_t __ceph_sync_read(struct inode *inode, loff_t *ki_pos,
11401159
req->r_start_latency,
11411160
req->r_end_latency,
11421161
read_len, ret);
1162+
/*
1163+
* Only record subvolume metrics for actual bytes read.
1164+
* ret == 0 means EOF (no data), not an I/O operation.
1165+
*/
1166+
if (ret > 0)
1167+
ceph_record_subvolume_io(inode, false,
1168+
req->r_start_latency,
1169+
req->r_end_latency,
1170+
ret);
11431171

11441172
if (ret > 0)
11451173
objver = req->r_version;
@@ -1385,12 +1413,23 @@ static void ceph_aio_complete_req(struct ceph_osd_request *req)
13851413

13861414
/* r_start_latency == 0 means the request was not submitted */
13871415
if (req->r_start_latency) {
1388-
if (aio_req->write)
1416+
if (aio_req->write) {
13891417
ceph_update_write_metrics(metric, req->r_start_latency,
13901418
req->r_end_latency, len, rc);
1391-
else
1419+
if (rc >= 0 && len)
1420+
ceph_record_subvolume_io(inode, true,
1421+
req->r_start_latency,
1422+
req->r_end_latency,
1423+
len);
1424+
} else {
13921425
ceph_update_read_metrics(metric, req->r_start_latency,
13931426
req->r_end_latency, len, rc);
1427+
if (rc > 0)
1428+
ceph_record_subvolume_io(inode, false,
1429+
req->r_start_latency,
1430+
req->r_end_latency,
1431+
rc);
1432+
}
13941433
}
13951434

13961435
put_bvecs(osd_data->bvec_pos.bvecs, osd_data->num_bvecs,
@@ -1614,12 +1653,23 @@ ceph_direct_read_write(struct kiocb *iocb, struct iov_iter *iter,
16141653
ceph_osdc_start_request(req->r_osdc, req);
16151654
ret = ceph_osdc_wait_request(&fsc->client->osdc, req);
16161655

1617-
if (write)
1656+
if (write) {
16181657
ceph_update_write_metrics(metric, req->r_start_latency,
16191658
req->r_end_latency, len, ret);
1620-
else
1659+
if (ret >= 0 && len)
1660+
ceph_record_subvolume_io(inode, true,
1661+
req->r_start_latency,
1662+
req->r_end_latency,
1663+
len);
1664+
} else {
16211665
ceph_update_read_metrics(metric, req->r_start_latency,
16221666
req->r_end_latency, len, ret);
1667+
if (ret > 0)
1668+
ceph_record_subvolume_io(inode, false,
1669+
req->r_start_latency,
1670+
req->r_end_latency,
1671+
ret);
1672+
}
16231673

16241674
size = i_size_read(inode);
16251675
if (!write) {
@@ -1872,6 +1922,11 @@ ceph_sync_write(struct kiocb *iocb, struct iov_iter *from, loff_t pos,
18721922
req->r_start_latency,
18731923
req->r_end_latency,
18741924
read_len, ret);
1925+
if (ret > 0)
1926+
ceph_record_subvolume_io(inode, false,
1927+
req->r_start_latency,
1928+
req->r_end_latency,
1929+
ret);
18751930

18761931
/* Ok if object is not already present */
18771932
if (ret == -ENOENT) {
@@ -2036,6 +2091,11 @@ ceph_sync_write(struct kiocb *iocb, struct iov_iter *from, loff_t pos,
20362091

20372092
ceph_update_write_metrics(&fsc->mdsc->metric, req->r_start_latency,
20382093
req->r_end_latency, len, ret);
2094+
if (ret >= 0 && write_len)
2095+
ceph_record_subvolume_io(inode, true,
2096+
req->r_start_latency,
2097+
req->r_end_latency,
2098+
write_len);
20392099
ceph_osdc_put_request(req);
20402100
if (ret != 0) {
20412101
doutc(cl, "osd write returned %d\n", ret);

0 commit comments

Comments
 (0)