From d32f6543be7b43a0d216180255628e5e490e3229 Mon Sep 17 00:00:00 2001
From: Roland Kammerer <roland.kammerer@linbit.com>
Date: Thu, 5 Jul 2018 11:20:32 +0200
Subject: [PATCH] drbd: expose openers via debugfs

So far we just wrote openers to the syslog, but that produces too much
spam. Now openers are exposed via:
/sys/kernel/debug/drbd/resources/$res/volumes/$volid/openers

We keep a list in the device. If the open counts drop to 0, we prune the
whole list. Otherwise we remove the oldest/first entry matching the
closing pid.

This should be as good as it gets, but it is not perfect. On one hand
the openening process might not be the closing one (mount vs. umount).
But we can assume that in these scenarios the open/close count drops to
0 and we remove all entries anyways. It could also happen that one
process (with the same pid) opens the device multiple times, and closes
it in reverse order. In such a case we would delete the first/oldest
entry, while we should have deleted the second one.
---
 drbd/drbd_debugfs.c | 19 +++++++++++++
 drbd/drbd_int.h     | 10 +++++++
 drbd/drbd_main.c    | 68 ++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 90 insertions(+), 7 deletions(-)

diff --git a/drbd/drbd_debugfs.c b/drbd/drbd_debugfs.c
index f1d4a19d..8f972ccb 100644
--- a/drbd/drbd_debugfs.c
+++ b/drbd/drbd_debugfs.c
@@ -1017,6 +1017,22 @@ static int device_oldest_requests_show(struct seq_file *m, void *ignored)
 	return 0;
 }
 
+static int device_openers_show(struct seq_file *m, void *ignored)
+{
+	struct drbd_device *device = m->private;
+	struct drbd_resource *resource = device->resource;
+	ktime_t now = ktime_get();
+	struct opener *tmp;
+
+	mutex_lock(&resource->open_release);
+	list_for_each_entry(tmp, &device->openers.list, list)
+		seq_printf(m, "%s\t%d\t%lld\n", tmp->comm, tmp->pid,
+			ktime_to_ms(ktime_sub(now, tmp->opened)));
+	mutex_unlock(&resource->open_release);
+
+	return 0;
+}
+
 static int device_data_gen_id_show(struct seq_file *m, void *ignored)
 {
 	struct drbd_device *device = m->private;
@@ -1189,6 +1205,7 @@ drbd_debugfs_device_attr(act_log_histogram)
 drbd_debugfs_device_attr(data_gen_id)
 drbd_debugfs_device_attr(io_frozen)
 drbd_debugfs_device_attr(ed_gen_id)
+drbd_debugfs_device_attr(openers)
 __drbd_debugfs_device_attr(req_timing, device_req_timing_write)
 
 void drbd_debugfs_device_add(struct drbd_device *device)
@@ -1228,6 +1245,7 @@ void drbd_debugfs_device_add(struct drbd_device *device)
 	vol_dcf(data_gen_id);
 	vol_dcf(io_frozen);
 	vol_dcf(ed_gen_id);
+	vol_dcf(openers);
 	drbd_dcf(device->debugfs_vol, device, req_timing, S_IRUSR | S_IWUSR);
 
 	/* Caller holds conf_update */
@@ -1252,6 +1270,7 @@ void drbd_debugfs_device_cleanup(struct drbd_device *device)
 	drbd_debugfs_remove(&device->debugfs_vol_data_gen_id);
 	drbd_debugfs_remove(&device->debugfs_vol_io_frozen);
 	drbd_debugfs_remove(&device->debugfs_vol_ed_gen_id);
+	drbd_debugfs_remove(&device->debugfs_vol_openers);
 	drbd_debugfs_remove(&device->debugfs_vol_req_timing);
 	drbd_debugfs_remove(&device->debugfs_vol);
 }
diff --git a/drbd/drbd_int.h b/drbd/drbd_int.h
index 1335639c..169a8079 100644
--- a/drbd/drbd_int.h
+++ b/drbd/drbd_int.h
@@ -1192,6 +1192,13 @@ struct submit_worker {
 	struct list_head peer_writes;
 };
 
+struct opener {
+	struct list_head list;
+	char comm[TASK_COMM_LEN];
+	pid_t pid;
+	ktime_t opened;
+};
+
 struct drbd_device {
 #ifdef PARANOIA
 	long magic;
@@ -1200,6 +1207,8 @@ struct drbd_device {
 	struct list_head peer_devices;
 	struct list_head pending_bitmap_io;
 
+	struct opener openers;
+
 	unsigned long flush_jif;
 #ifdef CONFIG_DEBUG_FS
 	struct dentry *debugfs_minor;
@@ -1210,6 +1219,7 @@ struct drbd_device {
 	struct dentry *debugfs_vol_data_gen_id;
 	struct dentry *debugfs_vol_io_frozen;
 	struct dentry *debugfs_vol_ed_gen_id;
+	struct dentry *debugfs_vol_openers;
 	struct dentry *debugfs_vol_req_timing;
 #endif
 
diff --git a/drbd/drbd_main.c b/drbd/drbd_main.c
index 9f87cd0c..7dd1cbd3 100644
--- a/drbd/drbd_main.c
+++ b/drbd/drbd_main.c
@@ -2513,6 +2513,54 @@ static enum ioc_rv inc_open_count(struct drbd_device *device, fmode_t mode)
 	return r;
 }
 
+static void __prune_or_free_openers(struct drbd_device *device, pid_t pid)
+{
+	struct opener *pos, *tmp;
+
+	list_for_each_entry_safe(pos, tmp, &device->openers.list, list) {
+		// if pid == 0, i.e., counts were 0, delete all entries, else the matching one
+		if (pid == 0 || pid == pos->pid) {
+			dynamic_drbd_dbg(device, "%sopeners del: %s(%d)\n", pid == 0 ? "" : "all ",
+					pos->comm, pos->pid);
+			list_del(&pos->list);
+			kfree(pos);
+
+			/* in case we remove a real process, stopp here, there might be multiple openers with the same pid */
+			/* this assumes that the oldest opener with the same pid releases first. "as good as it gets" */
+			if (pid != 0)
+				return;
+		}
+	}
+}
+
+static void free_openers(struct drbd_device *device) {
+	__prune_or_free_openers(device, 0);
+}
+
+/* caller needs to hold the the open_release_lock */
+static void add_opener(struct drbd_device *device)
+{
+	struct opener *opener;
+	int len = 0;
+
+	list_for_each_entry(opener, &device->openers.list, list)
+		if (++len > 100) { /* 100 ought to be enough for everybody */
+			dynamic_drbd_dbg(device, "openers: list full, do not add new opener\n");
+			return;
+		}
+
+	opener = kmalloc(sizeof(*opener), GFP_NOIO);
+	if (!opener) {
+		return;
+	}
+
+	get_task_comm(opener->comm, current);
+	opener->pid = task_pid_nr(current);
+	opener->opened = ktime_get();
+	list_add(&opener->list, &device->openers.list);
+	dynamic_drbd_dbg(device, "openers add: %s(%d)\n", opener->comm, opener->pid);
+}
+
 static int drbd_open(struct block_device *bdev, fmode_t mode)
 {
 	struct drbd_device *device = bdev->bd_disk->private_data;
@@ -2571,15 +2619,15 @@ static int drbd_open(struct block_device *bdev, fmode_t mode)
 		rv = ro_open_cond(device);
 	}
 out:
+	/* still keep mutex, but release ASAP */
+	if (!rv)
+		add_opener(device);
+
 	mutex_unlock(&resource->open_release);
 	if (rv) {
 		drbd_release(bdev->bd_disk, mode);
 		if (rv == -EAGAIN && !(mode & FMODE_NDELAY))
 			rv = -EMEDIUMTYPE;
-	} else {
-		char comm[TASK_COMM_LEN];
-		drbd_info(device, "open by: %s(%d)\n",
-				get_task_comm(comm, current), task_pid_nr(current));
 	}
 
 	return rv;
@@ -2602,7 +2650,6 @@ static DRBD_RELEASE_RETURN drbd_release(struct gendisk *gd, fmode_t mode)
 {
 	struct drbd_device *device = gd->private_data;
 	struct drbd_resource *resource = device->resource;
-	char comm[TASK_COMM_LEN];
 	int open_rw_cnt, open_ro_cnt;
 
 	mutex_lock(&resource->open_release);
@@ -2634,12 +2681,16 @@ static DRBD_RELEASE_RETURN drbd_release(struct gendisk *gd, fmode_t mode)
 					  drbd_set_st_err_str(rv));
 		}
 	}
+
+	/* if the open counts are 0, we free the whole list, otherwise we remove the specific pid */
+	__prune_or_free_openers(device,
+			(open_ro_cnt == 0 && open_rw_cnt == 0) ? 0 : task_pid_nr(current));
+
 	mutex_unlock(&resource->open_release);
 
 	kref_debug_put(&device->kref_debug, 3);
 	kref_put(&device->kref, drbd_destroy_device);  /* might destroy the resource as well */
-	drbd_info(device, "close by: %s(%d)\n",
-			get_task_comm(comm, current), task_pid_nr(current));
+
 #ifndef COMPAT_DRBD_RELEASE_RETURNS_VOID
 	return 0;
 #endif
@@ -2871,6 +2922,8 @@ void drbd_destroy_device(struct kref *kref)
 	/* cleanup stuff that may have been allocated during
 	 * device (re-)configuration or state changes */
 
+	free_openers(device);
+
 	lc_destroy(device->act_log);
 	for_each_peer_device_safe(peer_device, tmp, device) {
 		kref_debug_put(&peer_device->connection->kref_debug, 3);
@@ -3609,6 +3662,7 @@ enum drbd_ret_code drbd_create_device(struct drbd_config_context *adm_ctx, unsig
 	INIT_LIST_HEAD(&device->pending_master_completion[1]);
 	INIT_LIST_HEAD(&device->pending_completion[0]);
 	INIT_LIST_HEAD(&device->pending_completion[1]);
+	INIT_LIST_HEAD(&device->openers.list);
 
 	atomic_set(&device->pending_bitmap_work.n, 0);
 	spin_lock_init(&device->pending_bitmap_work.q_lock);
-- 
2.34.1

