>From cc52aa5c945350e0aa6bd3877fd3d1d00c163a30 Mon Sep 17 00:00:00 2001
From: Wen Congyang <wency@cn.fujitsu.com>
Date: Fri, 20 Jan 2012 08:54:50 +0800
Subject: [PATCH] implement new command diskio

Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
---
 defs.h        |   27 ++++
 global_data.c |    1 +
 help.c        |   24 +++
 kernel.c      |  455 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 506 insertions(+), 1 deletions(-)

diff --git a/defs.h b/defs.h
index 82d51e5..f95f443 100644
--- a/defs.h
+++ b/defs.h
@@ -1631,6 +1631,27 @@ struct offset_table {                    /* stash of commonly-used offsets */
 	long sched_entity_my_q;
 	long sched_entity_on_rq;
 	long task_struct_on_rq;
+
+	long class_devices;
+	long class_p;
+	long class_private_devices;
+	long device_knode_class;
+	long device_node;
+	long gendisk_dev;
+	long gendisk_kobj;
+	long gendisk_part0;
+	long gendisk_queue;
+	long hd_struct_dev;
+	long klist_k_list;
+	long klist_node_n_klist;
+	long klist_node_n_node;
+	long kobject_entry;
+	long kset_list;
+	long request_list_count;
+	long request_queue_in_flight;
+	long request_queue_rq;
+	long subsys_private_klist_devices;
+	long subsystem_kset;
 };
 
 struct size_table {         /* stash of commonly-used sizes */
@@ -1752,6 +1773,10 @@ struct size_table {         /* stash of commonly-used sizes */
 	long s390_stack_frame;
 	long percpu_data;
 	long sched_entity;
+	long subsystem;
+	long class_private;
+	long rq_in_flight;
+	long class_private_devices;
 };
 
 struct array_table {
@@ -3472,6 +3497,7 @@ void cmd_kmem(void);         /* memory.c */
 void cmd_search(void);       /* memory.c */
 void cmd_swap(void);         /* memory.c */
 void cmd_pte(void);          /* memory.c */
+void cmd_diskio(void);       /* memory.c */
 void cmd_ps(void);           /* task.c */
 void cmd_task(void);         /* task.c */
 void cmd_foreach(void);      /* task.c */
@@ -3933,6 +3959,7 @@ extern char *help_bt[];
 extern char *help_btop[];
 extern char *help_dev[];
 extern char *help_dis[];
+extern char *help_diskio[];
 extern char *help_eval[];
 extern char *help_exit[];
 extern char *help_extend[];
diff --git a/global_data.c b/global_data.c
index 98a5a79..2e7228d 100644
--- a/global_data.c
+++ b/global_data.c
@@ -76,6 +76,7 @@ struct command_table_entry linux_command_table[] = {
 	{"btop",    cmd_btop,    help_btop,    0},
 	{"dev",     cmd_dev,     help_dev,     0},
 	{"dis",     cmd_dis,     help_dis,     0},
+	{"diskio",  cmd_diskio,  help_diskio,  0},
 	{"eval",    cmd_eval,    help_eval,    0},
 	{"exit",    cmd_quit,    help_exit,    0},
 	{"extend",  cmd_extend,  help_extend,  0},
diff --git a/help.c b/help.c
index adaaea7..d94ca19 100644
--- a/help.c
+++ b/help.c
@@ -5423,6 +5423,30 @@ NULL
 };
 
 
+char *help_diskio[] = {
+"diskio",
+"disk I/O statistics",
+" ",
+"  This command dumps I/O statistics of all disks:",
+"    TOTAL: The total requests that have not been ended",
+"     READ: The total read requests that have not been ended",
+"    WRITE: The total write requests that have not been ended",
+"      DRV: The total requests that have been in the driver, but not end",
+"",
+"  Note: some kernel does not contain read/write requests, and the command",
+"  will output '-----'"
+"\nEXAMPLES",
+"    %s> diskio",
+"    MAJOR GENDISK            NAME RUQUEST QUEUE      TOTAL  READ WRITE   DRV",
+"      008 0xe00000010773ea80  sda 0x3000000109c9fbf0    12     0    12     0",
+"      008 0xe00000010773e680  sdb 0x3000000109c9f8a0     2     2     0     0",
+"      008 0xe000000107781d80  sdc 0x300000010c268050     6     0     6     6",
+"      008 0xe00000010773e080  sdd 0x300000010c26bbf0     0     0     0     0",
+"      008 0xe00000010773dc80  sde 0x300000010c3dd780     0     0     0     0",
+NULL
+};
+
+
 /*
  *  Find out what the help request is looking for, accepting aliases as well,
  *  and print the relevant strings from the appropriate help table.
diff --git a/kernel.c b/kernel.c
index dccef2c..12d0aea 100644
--- a/kernel.c
+++ b/kernel.c
@@ -582,7 +582,41 @@ kernel_init()
 	STRUCT_SIZE_INIT(mem_section, "mem_section");
 
 	BUG_bytes_init();
-	
+
+	MEMBER_OFFSET_INIT(class_devices, "class", "class_devices");
+	if (INVALID_MEMBER(class_devices))
+		MEMBER_OFFSET_INIT(class_devices, "class", "devices");
+	MEMBER_OFFSET_INIT(class_p, "class", "p");
+	MEMBER_OFFSET_INIT(class_private_devices, "class_private",
+		"class_devices");
+	MEMBER_OFFSET_INIT(device_knode_class, "device", "knode_class");
+	MEMBER_OFFSET_INIT(device_node, "device", "node");
+	MEMBER_OFFSET_INIT(device_type, "device", "type");
+	MEMBER_OFFSET_INIT(gendisk_dev, "gendisk", "dev");
+	if (INVALID_MEMBER(gendisk_dev))
+		MEMBER_OFFSET_INIT(gendisk_dev, "gendisk", "__dev");
+	MEMBER_OFFSET_INIT(gendisk_kobj, "gendisk", "kobj");
+	MEMBER_OFFSET_INIT(gendisk_part0, "gendisk", "part0");
+	MEMBER_OFFSET_INIT(gendisk_queue, "gendisk", "queue");
+	MEMBER_OFFSET_INIT(hd_struct_dev, "hd_struct", "__dev");
+	MEMBER_OFFSET_INIT(klist_k_list, "klist", "k_list");
+	MEMBER_OFFSET_INIT(klist_node_n_klist, "klist_node", "n_klist");
+	MEMBER_OFFSET_INIT(klist_node_n_node, "klist_node", "n_node");
+	MEMBER_OFFSET_INIT(kobject_entry, "kobject", "entry");
+	MEMBER_OFFSET_INIT(kset_list, "kset", "list");
+	MEMBER_OFFSET_INIT(request_list_count, "request_list", "count");
+	MEMBER_OFFSET_INIT(request_queue_in_flight, "request_queue",
+		"in_flight");
+	MEMBER_OFFSET_INIT(request_queue_rq, "request_queue", "rq");
+	MEMBER_OFFSET_INIT(subsys_private_klist_devices, "subsys_private",
+		"klist_devices");
+	MEMBER_OFFSET_INIT(subsystem_kset, "subsystem", "kset");
+	STRUCT_SIZE_INIT(subsystem, "subsystem");
+	STRUCT_SIZE_INIT(class_private, "class_private");
+	MEMBER_SIZE_INIT(rq_in_flight, "request_queue", "in_flight");
+	MEMBER_SIZE_INIT(class_private_devices, "class_private",
+		"class_devices");
+
 	kt->flags &= ~PRE_KERNEL_INIT;
 }
 
@@ -7389,3 +7423,422 @@ paravirt_init(void)
 		kt->flags |= ARCH_PVOPS;
 	}
 }
+
+/*
+ * If the disk's name is started with these strings, we will skip it and do not
+ * display its statistics.
+ */
+static char *skipped_disk_name[] = {
+	"ram",
+	"loop",
+	NULL
+};
+
+static int is_skipped_disk(char *name)
+{
+	char **p = skipped_disk_name;
+
+	while (*p) {
+		if (strncmp(name, *p, strlen(*p)) == 0)
+			return TRUE;
+		p++;
+	}
+
+	return FALSE;
+}
+
+struct diskio {
+    int read;
+    int write;
+};
+
+struct iter {
+	/* If the kernel uses klist, the address should be klist.k_list */
+	long head_address;
+	long current_address;
+	long type_address; /* the address of symbol "disk_type" */
+
+	/*
+	 * If it is true, it means request_list.count[2] contains async/sync
+	 * requests.
+	 */
+	int sync_count;
+	int diskname_len;
+
+	unsigned long (*next_disk)(struct iter *);
+
+	/*
+	 * The argument is the address of request_queue, and the function
+	 * returns the total requests in the driver(not ended)
+	 */
+	unsigned int (*get_in_flight)(unsigned long);
+
+	/*
+	 * this function reads request_list.count[2], and the first argument
+	 * is the address of request_queue.
+	 */
+	void (*get_diskio)(unsigned long , struct diskio *);
+
+	/*
+	 * check if device.type == &disk_type
+	 *
+	 * old kernel(version <= 2.6.24) does not have the symbol "disk_type",
+	 * and this callback should be null.
+	 */
+	int (*match)(struct iter *, unsigned long);
+
+	/*
+	 * If the kernel uses list, the argument is the address of list_head,
+	 * otherwise, the argument is the address of klist_node.
+	 */
+	unsigned long (*get_gendisk)(unsigned long);
+};
+
+/* kernel version <= 2.6.24 */
+static unsigned long get_gendisk_1(unsigned long entry)
+{
+	return entry - OFFSET(kobject_entry) - OFFSET(gendisk_kobj);
+}
+
+/* 2.6.24 < kernel version <= 2.6.27 */
+static unsigned long get_gendisk_2(unsigned long entry)
+{
+	return entry - OFFSET(device_node) - OFFSET(gendisk_dev);
+}
+
+/* kernel version > 2.6.27 && struct gendisk contains dev/__dev */
+static unsigned long get_gendisk_3(unsigned long entry)
+{
+	return entry - OFFSET(device_knode_class) - OFFSET(gendisk_dev);
+}
+
+/* kernel version > 2.6.27 && struct gendisk does not contain dev/__dev */
+static unsigned long get_gendisk_4(unsigned long entry)
+{
+	return entry - OFFSET(device_knode_class) - OFFSET(hd_struct_dev) -
+		OFFSET(gendisk_part0);
+}
+
+/* 2.6.24 < kernel version <= 2.6.27 */
+static int match_list(struct iter *i, unsigned long entry)
+{
+	unsigned long device_address;
+	unsigned long device_type;
+
+	device_address = entry - OFFSET(device_node);
+	readmem(device_address + OFFSET(device_type), KVADDR, &device_type,
+		sizeof(device_type), "device.type", FAULT_ON_ERROR);
+	if (device_type != i->type_address)
+		return FALSE;
+
+	return TRUE;
+}
+
+/* kernel version > 2.6.27 */
+static int match_klist(struct iter *i, unsigned long entry)
+{
+	unsigned long device_address;
+	unsigned long device_type;
+
+	device_address = entry - OFFSET(device_knode_class);
+	readmem(device_address + OFFSET(device_type), KVADDR, &device_type,
+		sizeof(device_type), "device.type", FAULT_ON_ERROR);
+	if (device_type != i->type_address)
+		return FALSE;
+
+	return TRUE;
+}
+
+/* old kernel(version <= 2.6.27): list */
+static unsigned long next_disk_list(struct iter *i)
+{
+	unsigned long list_head_address, next_address;
+
+	if (i->current_address) {
+		list_head_address = i->current_address;
+	} else {
+		list_head_address = i->head_address;
+	}
+
+again:
+	/* read list_head.next */
+	readmem(list_head_address + OFFSET(list_head_next), KVADDR,
+		&next_address, sizeof(next_address), "list_head.next",
+		FAULT_ON_ERROR);
+
+	if (next_address == i->head_address)
+		return 0;
+	if (i->match && !i->match(i, next_address))
+		goto again;
+
+	i->current_address = next_address;
+	return i->get_gendisk(next_address);
+}
+
+/* new kernel(version > 2.6.27): klist */
+static unsigned long next_disk_klist(struct iter* i)
+{
+	unsigned long klist_node_address, list_head_address, next_address;
+	unsigned long n_klist;
+
+	if (i->current_address) {
+		list_head_address = i->current_address;
+	} else {
+		list_head_address = i->head_address;
+	}
+
+again:
+	/* read list_head.next */
+	readmem(list_head_address + OFFSET(list_head_next), KVADDR,
+		&next_address, sizeof(next_address), "list_head.next",
+		FAULT_ON_ERROR);
+
+	/* skip dead klist_node */
+	while(next_address != i->head_address) {
+		klist_node_address = next_address - OFFSET(klist_node_n_node);
+		readmem(klist_node_address + OFFSET(klist_node_n_klist), KVADDR,
+			&n_klist, sizeof(n_klist), "klist_node.n_klist",
+			FAULT_ON_ERROR);
+		if (!(n_klist & 1))
+			break;
+
+		/* the klist_node is dead, skip to next klist_node */
+		readmem(next_address + OFFSET(list_head_next), KVADDR,
+			&next_address, sizeof(next_address), "list_head.next",
+			FAULT_ON_ERROR);
+	}
+
+	if (next_address == i->head_address)
+		return 0;
+
+	if (i->match && !i->match(i, klist_node_address)) {
+		list_head_address = next_address;
+		goto again;
+	}
+
+	i->current_address = next_address;
+	return i->get_gendisk(klist_node_address);
+}
+
+/* read request_queue.rq.count[2] */
+static void get_diskio_1(unsigned long rq, struct diskio *io)
+{
+	int count[2];
+
+	readmem(rq + OFFSET(request_queue_rq) + OFFSET(request_list_count),
+		KVADDR, count, sizeof(int) * 2, "request_list.count",
+		FAULT_ON_ERROR);
+
+	io->read = count[0];
+	io->write = count[1];
+}
+
+/* request_queue.in_flight contains total requests */
+static unsigned int get_in_flight_1(unsigned long rq)
+{
+	unsigned int in_flight;
+
+	readmem(rq+ OFFSET(request_queue_in_flight), KVADDR, &in_flight,
+		sizeof(uint), "request_queue.in_flight", FAULT_ON_ERROR);
+	return in_flight;
+}
+
+/* request_queue.in_flight[2] contains read/write requests */
+static unsigned int get_in_flight_2(unsigned long rq)
+{
+	unsigned int in_flight[2];
+
+	readmem(rq+ OFFSET(request_queue_in_flight), KVADDR, in_flight,
+		sizeof(uint) * 2, "request_queue.in_flight", FAULT_ON_ERROR);
+	return in_flight[0] + in_flight[1];
+}
+
+static void init_iter(struct iter *i)
+{
+	ARRAY_LENGTH_INIT(i->diskname_len, gendisk.disk_name,
+		"gendisk.disk_name", NULL, sizeof(char));
+	if (i->diskname_len < 0 || i->diskname_len > BUFSIZE) {
+		error(FATAL, "invalid diskname's length\n");
+		return;
+	}
+
+	i->current_address = 0;
+
+	/* check whether BLK_RW_SYNC exists */
+	i->sync_count =
+		get_symbol_type("BLK_RW_SYNC", NULL, NULL) == TYPE_CODE_ENUM;
+
+	if (SIZE(rq_in_flight) == sizeof(int)) {
+		i->get_in_flight = get_in_flight_1;
+	} else if (SIZE(rq_in_flight) == sizeof(int) * 2) {
+		i->get_in_flight = get_in_flight_2;
+	} else {
+		error(FATAL, "invalid request_queue.in_flight's size\n");
+		return;
+	}
+	i->get_diskio = get_diskio_1;
+
+	if (symbol_exists("block_subsys") || symbol_exists("block_kset")) {
+		/* kernel version <= 2.6.24 */
+		unsigned long block_subsys_addr;
+
+		if (symbol_exists("block_subsys"))
+			block_subsys_addr = symbol_value("block_subsys");
+		else
+			block_subsys_addr = symbol_value("block_kset");
+		if (VALID_STRUCT(subsystem))
+			i->head_address = block_subsys_addr +
+				OFFSET(subsystem_kset) + OFFSET(kset_list);
+		else
+			i->head_address = block_subsys_addr + OFFSET(kset_list);
+		i->type_address = 0;
+		i->next_disk = next_disk_list;
+		i->match = NULL;
+		i->get_gendisk = get_gendisk_1;
+	} else if (symbol_exists("block_class")) {
+		unsigned long block_class_addr = symbol_value("block_class");
+
+		i->type_address = symbol_value("disk_type");
+		if (VALID_MEMBER(class_devices) ||
+		   (VALID_MEMBER(class_private_devices) &&
+		      SIZE(class_private_devices) == SIZE(list_head))) {
+			/* 2.6.24 < kernel version <= 2.6.27, list */
+			if (!VALID_STRUCT(class_private)) {
+				/* 2.6.24 < kernel version <= 2.6.26 */
+				i->head_address = block_class_addr +
+					OFFSET(class_devices);
+			} else {
+				/* kernel version is 2.6.27 */
+				unsigned long class_private_addr;
+
+				readmem(block_class_addr + OFFSET(class_p),
+					KVADDR, &class_private_addr,
+					sizeof(class_private_addr), "class.p",
+					FAULT_ON_ERROR);
+				i->head_address = class_private_addr +
+					OFFSET(class_private_devices);
+			}
+			i->next_disk = next_disk_list;
+			i->match = match_list;
+			i->get_gendisk = get_gendisk_2;
+		} else {
+			/* kernel version > 2.6.27, klist */
+			unsigned long class_private_addr;
+			readmem(block_class_addr + OFFSET(class_p), KVADDR,
+				&class_private_addr, sizeof(class_private_addr),
+				"class.p", FAULT_ON_ERROR);
+
+			if (VALID_STRUCT(class_private)) {
+				/* 2.6.27 < kernel version <= 2.6.37-rc2 */
+				i->head_address = class_private_addr +
+					OFFSET(class_private_devices);
+			} else {
+				/* kernel version > 2.6.37-rc2 */
+				i->head_address = class_private_addr +
+					OFFSET(subsys_private_klist_devices);
+			}
+			i->head_address += OFFSET(klist_k_list);
+			i->next_disk = next_disk_klist;
+			i->match = match_klist;
+			if (VALID_MEMBER(gendisk_dev))
+				i->get_gendisk = get_gendisk_3;
+			else
+				i->get_gendisk = get_gendisk_4;
+		}
+	} else {
+		error(FATAL, "unsupported kernel version\n");
+		return;
+	}
+}
+
+static void display_one_diskio(struct iter *i, unsigned long gendisk)
+{
+	char disk_name[BUFSIZE + 1];
+	char buf0[BUFSIZE];
+	char buf1[BUFSIZE];
+	char buf2[BUFSIZE];
+	char buf3[BUFSIZE];
+	char buf4[BUFSIZE];
+	char buf5[BUFSIZE];
+	int major;
+	unsigned long queue_addr;
+	unsigned int in_flight;
+	struct diskio io;
+
+	memset(disk_name, 0, BUFSIZE + 1);
+	readmem(gendisk + OFFSET(gendisk_disk_name), KVADDR, disk_name,
+		i->diskname_len, "gen_disk.disk_name", FAULT_ON_ERROR);
+	if (is_skipped_disk(disk_name))
+		return;
+
+	readmem(gendisk + OFFSET(gendisk_queue), KVADDR, &queue_addr,
+		sizeof(ulong), "gen_disk.queue", FAULT_ON_ERROR);
+	readmem(gendisk + OFFSET(gendisk_major), KVADDR, &major, sizeof(int),
+		"gen_disk.major", FAULT_ON_ERROR);
+	i->get_diskio(queue_addr, &io);
+	in_flight = i->get_in_flight(queue_addr);
+
+	fprintf(fp, "%s%s0x%s%s%s%s0x%s%s%5d%s%s%s%s%s%5u\n",
+		mkstring(buf0, 5, RJUST|INT_DEC, (char *)(unsigned long)major),
+		space(MINSPACE),
+		mkstring(buf1, VADDR_PRLEN, LJUST|LONG_HEX, (char *)gendisk),
+		space(MINSPACE),
+		mkstring(buf2, 4, RJUST, disk_name),
+		space(MINSPACE),
+		mkstring(buf3, VADDR_PRLEN <= 11 ? 11 : VADDR_PRLEN,
+			 LJUST|LONG_HEX, (char *)queue_addr),
+		space(MINSPACE),
+		io.read + io.write,
+		space(MINSPACE),
+		i->sync_count ? "-----" : mkstring(buf4, 5, RJUST|INT_DEC,
+			(char *)(unsigned long)io.read),
+		space(MINSPACE),
+		i->sync_count ? "-----" : mkstring(buf5, 5, RJUST|INT_DEC,
+			(char *)(unsigned long)io.write),
+		space(MINSPACE),
+		in_flight);
+}
+
+static void display_all_diskio(void)
+{
+	struct iter i;
+	unsigned long gendisk;
+	char buf0[BUFSIZE];
+	char buf1[BUFSIZE];
+	char buf2[BUFSIZE];
+	char buf3[BUFSIZE];
+	char buf4[BUFSIZE];
+	char buf5[BUFSIZE];
+
+	init_iter(&i);
+
+	fprintf(fp, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
+		"MAJOR",
+		space(MINSPACE),
+		mkstring(buf0, VADDR_PRLEN + 2, LJUST, "GENDISK"),
+		space(MINSPACE),
+		"NAME",
+		space(MINSPACE),
+		mkstring(buf1, VADDR_PRLEN <= 11 ? 13 : VADDR_PRLEN + 2, LJUST,
+			"RUQUEST QUEUE"),
+		space(MINSPACE),
+		mkstring(buf2, 5, RJUST, "TOTAL"),
+		space(MINSPACE),
+		mkstring(buf3, 5, RJUST, "READ"),
+		space(MINSPACE),
+		mkstring(buf4, 5, RJUST, "WRITE"),
+		space(MINSPACE),
+		mkstring(buf5, 5, RJUST, "DRV"));
+
+	while((gendisk = i.next_disk(&i)) != 0) {
+		display_one_diskio(&i, gendisk);
+	}
+}
+
+void cmd_diskio(void)
+{
+	if (argcnt == 1)
+		display_all_diskio();
+	else
+		cmd_usage(pc->curcmd, SYNOPSIS);
+}
-- 
1.7.1

