Blob Blame History Raw
commit f852f5ce4d28f88308f0e555c067e63e3edd7f37
Author: Dave Anderson <anderson@redhat.com>
Date:   Fri Nov 3 09:21:22 2017 -0400

    Fix for a "ps -l" regression introduced by the new "ps -y" option
    introduced above.  Without the patch, the -l option generates a
    segmentation violation if not accompanied by a -C cpu specifier
    option.
    (vinayakm.list@gmail.com)

diff --git a/task.c b/task.c
index 5754159..f2628b7 100644
--- a/task.c
+++ b/task.c
@@ -3485,7 +3485,7 @@ show_last_run(struct task_context *tc, struct psinfo *psi)
 	sprintf(format, "[%c%dll%c] ", '%', c, 
 		pc->output_radix == 10 ? 'u' : 'x');
 
-	if (psi) {
+	if (psi && psi->cpus) {
 		for (c = others = 0; c < kt->cpus; c++) {
 			if (!NUM_IN_BITMAP(psi->cpus, c))
 				continue;

commit 7ac1368cdca0fc2013bb3963456fcd2574c7cdd7
Author: Dave Anderson <anderson@redhat.com>
Date:   Mon Nov 6 10:48:40 2017 -0500

    Fix for the "kmem -i" and "kmem -V" options in Linux 4.8 and later
    kernels containing commit 75ef7184053989118d3814c558a9af62e7376a58,
    titled "mm, vmstat: add infrastructure for per-node vmstats".
    Without the patch, the CACHED line of "kmem -i" shows 0, and the
    VM_STAT section of "kmem -V" is missing entirely.
    (vinayakm.list@gmail.com)

diff --git a/memory.c b/memory.c
index ebd671a..3097558 100644
--- a/memory.c
+++ b/memory.c
@@ -17340,30 +17340,43 @@ vm_stat_init(void)
 	int c ATTRIBUTE_UNUSED;
         struct gnu_request *req;
 	char *start;
-	long enum_value;
+	long enum_value, zc = -1;
+	int split_vmstat = 0, ni = 0;
 
 	if (vt->flags & VM_STAT)
 		return TRUE;
 
-	if ((vt->nr_vm_stat_items == -1) || !symbol_exists("vm_stat"))
+	if ((vt->nr_vm_stat_items == -1) ||
+		(!symbol_exists("vm_stat") && !symbol_exists("vm_zone_stat")))
 		goto bailout;
 
         /*
          *  look for type: type = atomic_long_t []
          */
 	if (LKCD_KERNTYPES()) {
-        	if (!symbol_exists("vm_stat"))
+		if ((!symbol_exists("vm_stat") &&
+				!symbol_exists("vm_zone_stat")))
 			goto bailout;
 		/* 
 		 *  Just assume that vm_stat is an array; there is
 		 *  no symbol info in a kerntypes file. 
 		 */
 	} else {
-		if (!symbol_exists("vm_stat") ||
-		    get_symbol_type("vm_stat", NULL, NULL) != TYPE_CODE_ARRAY)
+		if (symbol_exists("vm_stat") &&
+		    get_symbol_type("vm_stat", NULL, NULL) == TYPE_CODE_ARRAY) {
+			vt->nr_vm_stat_items =
+				get_array_length("vm_stat", NULL, 0);
+		} else if (symbol_exists("vm_zone_stat") &&
+			get_symbol_type("vm_zone_stat",
+			NULL, NULL) == TYPE_CODE_ARRAY) {
+			vt->nr_vm_stat_items =
+				get_array_length("vm_zone_stat", NULL, 0)
+				+ get_array_length("vm_node_stat", NULL, 0);
+			split_vmstat = 1;
+			enumerator_value("NR_VM_ZONE_STAT_ITEMS", &zc);
+		} else {
 			goto bailout;
-
-		vt->nr_vm_stat_items = get_array_length("vm_stat", NULL, 0);
+		}
 	}
 
         open_tmpfile();
@@ -17372,6 +17385,14 @@ vm_stat_init(void)
         req->name = "zone_stat_item";
         req->flags = GNU_PRINT_ENUMERATORS;
         gdb_interface(req);
+
+	if (split_vmstat) {
+		req->command = GNU_GET_DATATYPE;
+		req->name = "node_stat_item";
+		req->flags = GNU_PRINT_ENUMERATORS;
+		gdb_interface(req);
+	}
+
         FREEBUF(req);
 
 	stringlen = 1;
@@ -17383,11 +17404,17 @@ vm_stat_init(void)
 			continue;
 		clean_line(buf);
 		c = parse_line(buf, arglist);
-		if (STREQ(arglist[0], "NR_VM_ZONE_STAT_ITEMS")) {
+		if ((!split_vmstat &&
+			STREQ(arglist[0], "NR_VM_ZONE_STAT_ITEMS")) ||
+			(split_vmstat &&
+			STREQ(arglist[0], "NR_VM_NODE_STAT_ITEMS"))) {
 			if (LKCD_KERNTYPES())
 				vt->nr_vm_stat_items = 
 					MAX(atoi(arglist[2]), count);
 			break;
+		} else if (split_vmstat &&
+			STREQ(arglist[0], "NR_VM_ZONE_STAT_ITEMS")) {
+			continue;
 		} else {
 			stringlen += strlen(arglist[0]);
 			count++;
@@ -17409,18 +17436,24 @@ vm_stat_init(void)
                 if (strstr(buf, "{") || strstr(buf, "}"))
                         continue;
 		c = parse_line(buf, arglist);
-		if (enumerator_value(arglist[0], &enum_value))
-			i = enum_value;
-		else {
+		if (!enumerator_value(arglist[0], &enum_value)) {
 			close_tmpfile();
 			goto bailout;
 		}
+
+		i = ni + enum_value;
+		if (!ni && (enum_value == zc)) {
+			ni = zc;
+			continue;
+		}
+
 		if (i < vt->nr_vm_stat_items) {
 			vt->vm_stat_items[i] = start;
 			strcpy(start, arglist[0]);
 			start += strlen(arglist[0]) + 1;
 		}
         }
+
 	close_tmpfile();
 
 	vt->flags |= VM_STAT;
@@ -17443,39 +17476,61 @@ dump_vm_stat(char *item, long *retval, ulong zone)
 	ulong *vp;
 	ulong location;
 	int i, maxlen, len;
+	long tc, zc = 0, nc = 0;
+	int split_vmstat = 0;
 
 	if (!vm_stat_init()) {
 		if (!item)
 			if (CRASHDEBUG(1))
-				error(INFO, 
+				error(INFO,
 			    	    "vm_stat not available in this kernel\n");
 		return FALSE;
 	}
 
 	buf = GETBUF(sizeof(ulong) * vt->nr_vm_stat_items);
 
-	location = zone ? zone : symbol_value("vm_stat");
-
-	readmem(location, KVADDR, buf, 
-	    sizeof(ulong) * vt->nr_vm_stat_items, 
-	    "vm_stat", FAULT_ON_ERROR);
+	if (symbol_exists("vm_node_stat") && symbol_exists("vm_zone_stat"))
+		split_vmstat = 1;
+	else
+		location = zone ? zone : symbol_value("vm_stat");
+
+	if (split_vmstat) {
+		enumerator_value("NR_VM_ZONE_STAT_ITEMS", &zc);
+		location = zone ? zone : symbol_value("vm_zone_stat");
+		readmem(location, KVADDR, buf,
+			sizeof(ulong) * zc,
+			"vm_zone_stat", FAULT_ON_ERROR);
+		if (!zone) {
+			location = symbol_value("vm_node_stat");
+			enumerator_value("NR_VM_NODE_STAT_ITEMS", &nc);
+			readmem(location, KVADDR, buf + (sizeof(ulong) * zc),
+				sizeof(ulong) * nc,
+				"vm_node_stat", FAULT_ON_ERROR);
+		}
+		tc = zc + nc;
+	} else {
+		readmem(location, KVADDR, buf,
+			sizeof(ulong) * vt->nr_vm_stat_items,
+			"vm_stat", FAULT_ON_ERROR);
+		tc = vt->nr_vm_stat_items;
+	}
 
 	if (!item) {
 		if (!zone)
 			fprintf(fp, "  VM_STAT:\n");
-		for (i = maxlen = 0; i < vt->nr_vm_stat_items; i++)
+		for (i = maxlen = 0; i < tc; i++)
 			if ((len = strlen(vt->vm_stat_items[i])) > maxlen)
 				maxlen = len;
 		vp = (ulong *)buf;
-		for (i = 0; i < vt->nr_vm_stat_items; i++)
-			fprintf(fp, "%s%s: %ld\n", 
+		for (i = 0; i < tc; i++)
+			fprintf(fp, "%s%s: %ld\n",
 				space(maxlen - strlen(vt->vm_stat_items[i])),
 				 vt->vm_stat_items[i], vp[i]);
 		return TRUE;
 	}
 
 	vp = (ulong *)buf;
-	for (i = 0; i < vt->nr_vm_stat_items; i++) {
+	for (i = 0; i < tc; i++) {
 		if (STREQ(vt->vm_stat_items[i], item)) {
 			*retval = vp[i];
 			return TRUE;

commit 333df037bc72aa81faf0904aaea29d43be2c724d
Author: Dave Anderson <anderson@redhat.com>
Date:   Mon Nov 6 11:01:45 2017 -0500

    Fix for Linux 4.11 and later kernels that contain kernel commit
    4b3ef9daa4fc0bba742a79faecb17fdaaead083b, titled "mm/swap: split
    swap cache into 64MB trunks".  Without the patch, the CACHED line
    of "kmem -i" may show nonsensical data.
    (vinayakm.list@gmail.com)

diff --git a/memory.c b/memory.c
index 3097558..7537c43 100644
--- a/memory.c
+++ b/memory.c
@@ -8236,7 +8236,44 @@ dump_kmeminfo(void)
 		char *swapper_space = GETBUF(SIZE(address_space));
 
 		swapper_space_nrpages = 0;
-		if (symbol_exists("swapper_spaces") && 
+		if (symbol_exists("nr_swapper_spaces") &&
+			(len = get_array_length("nr_swapper_spaces",
+				NULL, 0))) {
+			char *nr_swapper_space =
+				GETBUF(len * sizeof(unsigned int));
+			readmem(symbol_value("nr_swapper_spaces"), KVADDR,
+				nr_swapper_space,  len * sizeof(unsigned int),
+				"nr_swapper_space", RETURN_ON_ERROR);
+			for (i = 0; i < len; i++) {
+				int j;
+				unsigned long sa;
+				unsigned int banks = UINT(nr_swapper_space +
+					(i * sizeof(unsigned int)));
+
+				if (!banks)
+					continue;
+
+				readmem(symbol_value("swapper_spaces") +
+					(i * sizeof(void *)),KVADDR,
+					&sa, sizeof(void *),
+					"swapper_space", RETURN_ON_ERROR);
+
+				if (!sa)
+					continue;
+
+				for (j = 0; j < banks; j++) {
+					readmem(sa + j * SIZE(address_space),
+						KVADDR, swapper_space,
+						SIZE(address_space),
+						"swapper_space",
+						RETURN_ON_ERROR);
+					swapper_space_nrpages +=
+						ULONG(swapper_space +
+						OFFSET(address_space_nrpages));
+				}
+			}
+			FREEBUF(nr_swapper_space);
+		} else if (symbol_exists("swapper_spaces") &&
 		    (len = get_array_length("swapper_spaces", NULL, 0))) {
 			for (i = 0; i < len; i++) {
 		    		if (!readmem(symbol_value("swapper_spaces") + 
@@ -8253,7 +8290,7 @@ dump_kmeminfo(void)
 		    RETURN_ON_ERROR))
 			swapper_space_nrpages = ULONG(swapper_space + 
 				OFFSET(address_space_nrpages));
-			
+
 		page_cache_size = nr_file_pages - swapper_space_nrpages -
 			buffer_pages;
 		FREEBUF(swapper_space);

commit 613e5c7d6998c61880498537b4f288ef095cbe14
Author: Dave Anderson <anderson@redhat.com>
Date:   Mon Nov 6 15:12:59 2017 -0500

    Implemented a new "dev -D" option that is the same as "dev -d", but
    filters out the display of disks that have no I/O in progress.
    (oleksandr@redhat.com)

diff --git a/dev.c b/dev.c
index e46081e..3db898a 100644
--- a/dev.c
+++ b/dev.c
@@ -31,7 +31,7 @@ static const char *pci_strclass (uint, char *);
 static const char *pci_strvendor(uint, char *); 
 static const char *pci_strdev(uint, uint, char *); 
 
-static void diskio_option(void);
+static void diskio_option(ulong flags);
  
 static struct dev_table {
         ulong flags;
@@ -42,6 +42,9 @@ struct dev_table *dt = &dev_table;
 #define DEV_INIT    0x1
 #define DISKIO_INIT 0x2
 
+#define DIOF_ALL	1 << 0
+#define DIOF_NONZERO	1 << 1
+
 void
 dev_init(void)
 {
@@ -93,11 +96,15 @@ cmd_dev(void)
 
 	flags = 0;
 
-        while ((c = getopt(argcnt, args, "dpi")) != EOF) {
+        while ((c = getopt(argcnt, args, "dDpi")) != EOF) {
                 switch(c)
                 {
 		case 'd':
-			diskio_option();
+			diskio_option(DIOF_ALL);
+			return;
+
+		case 'D':
+			diskio_option(DIOF_NONZERO);
 			return;
 
 		case 'i':
@@ -4002,7 +4009,7 @@ init_iter(struct iter *i)
 }
 
 static void 
-display_one_diskio(struct iter *i, unsigned long gendisk)
+display_one_diskio(struct iter *i, unsigned long gendisk, ulong flags)
 {
 	char disk_name[BUFSIZE + 1];
 	char buf0[BUFSIZE];
@@ -4028,6 +4035,10 @@ display_one_diskio(struct iter *i, unsigned long gendisk)
 		"gen_disk.major", FAULT_ON_ERROR);
 	i->get_diskio(queue_addr, &io);
 
+	if ((flags & DIOF_NONZERO)
+		&& (io.read + io.write == 0))
+		return;
+
 	fprintf(fp, "%s%s%s  %s%s%s%s  %s%5d%s%s%s%s%s",
 		mkstring(buf0, 5, RJUST|INT_DEC, (char *)(unsigned long)major),
 		space(MINSPACE),
@@ -4055,7 +4066,7 @@ display_one_diskio(struct iter *i, unsigned long gendisk)
 }
 
 static void 
-display_all_diskio(void)
+display_all_diskio(ulong flags)
 {
 	struct iter i;
 	unsigned long gendisk;
@@ -4089,7 +4100,7 @@ display_all_diskio(void)
 		mkstring(buf5, 5, RJUST, "DRV"));
 
 	while ((gendisk = i.next_disk(&i)) != 0)
-		display_one_diskio(&i, gendisk);
+		display_one_diskio(&i, gendisk, flags);
 }
 
 static 
@@ -4149,8 +4160,8 @@ void diskio_init(void)
 }
 
 static void 
-diskio_option(void)
+diskio_option(ulong flags)
 {
 	diskio_init();
-	display_all_diskio();
+	display_all_diskio(flags);
 }
diff --git a/help.c b/help.c
index f7f61a1..fa01bfb 100644
--- a/help.c
+++ b/help.c
@@ -2722,7 +2722,7 @@ NULL
 char *help_dev[] = {
 "dev",
 "device data",
-"[-i | -p | -d]",
+"[-i | -p | -d | -D]",
 "  If no argument is entered, this command dumps character and block",
 "  device data.\n",
 "    -i  display I/O port usage; on 2.4 kernels, also display I/O memory usage.",
@@ -2736,6 +2736,7 @@ char *help_dev[] = {
 "           DRV: I/O requests that are in-flight in the device driver.",
 "                If the device driver uses blk-mq interface, this field",
 "                shows N/A(MQ).",
+"    -D  same as -d, but filter out disks with no in-progress I/O requests.",
 "\nEXAMPLES",
 "  Display character and block device data:\n",
 "    %s> dev",

commit 57eaba59bff54ab3158d3a909e9f64551e27accf
Author: Dave Anderson <anderson@redhat.com>
Date:   Wed Nov 8 14:22:16 2017 -0500

    If a line number request for a module text address initially fails,
    force the embedded gdb module to complete its two-stage strategy
    used for reading debuginfo symbol tables from module object files,
    and then retry the line number extraction.  This automatically does
    what the "mod -r" or "crash --readnow" options accomplish.
    (anderson@redhat.com)

diff --git a/defs.h b/defs.h
index 967fce0..18f36b3 100644
--- a/defs.h
+++ b/defs.h
@@ -2688,6 +2688,7 @@ struct load_module {
 	struct syment *mod_init_symend;
 	ulong mod_percpu;
 	ulong mod_percpu_size;
+	struct objfile *loaded_objfile;
 };
 
 #define IN_MODULE(A,L) \
@@ -4479,6 +4480,7 @@ struct gnu_request {
     		struct symbol *sym;
     		struct objfile *obj;
   	} global_iterator;
+	struct load_module *lm;
 };
 
 /*
diff --git a/gdb-7.6.patch b/gdb-7.6.patch
index 094f01a..6aeffda 100644
--- a/gdb-7.6.patch
+++ b/gdb-7.6.patch
@@ -2323,3 +2323,72 @@ diff -up gdb-7.6/opcodes/configure.orig gdb-7.6/opcodes/configure
      NO_WERROR="-Wno-error"
  fi
  
+--- gdb-7.6/gdb/symtab.c.orig
++++ gdb-7.6/gdb/symtab.c
+@@ -5266,6 +5266,7 @@ gdb_get_line_number(struct gnu_request *
+ {
+         struct symtab_and_line sal;
+ 	struct symbol *sym;
++	struct objfile *objfile;
+         CORE_ADDR pc;
+ 
+ #define LASTCHAR(s)      (s[strlen(s)-1])
+@@ -5281,8 +5282,22 @@ gdb_get_line_number(struct gnu_request *
+         sal = find_pc_line(pc, 0);
+ 
+ 	if (!sal.symtab) {
+-		req->buf[0] = '\0';
+-		return;
++		/*
++		 *  If a module address line number can't be found, it's typically
++		 *  due to its addrmap still containing offset values because its 
++		 *  objfile doesn't have full symbols loaded.
++		 */
++		if (req->lm) {
++			objfile = req->lm->loaded_objfile;
++			if (!objfile_has_full_symbols(objfile) && objfile->sf) { 
++				objfile->sf->qf->expand_all_symtabs(objfile);
++				sal = find_pc_line(pc, 0);
++			}
++		}
++		if (!sal.symtab) {
++			req->buf[0] = '\0';
++			return;
++		}
+ 	}
+ 
+         if (sal.symtab->filename && sal.symtab->dirname) {
+@@ -5557,7 +5572,6 @@ struct load_module *gdb_current_load_mod
+ static void 
+ gdb_add_symbol_file(struct gnu_request *req)
+ {
+-	register struct objfile *loaded_objfile = NULL;
+ 	register struct objfile *objfile;
+ 	register struct minimal_symbol *m;
+ 	struct load_module *lm;
+@@ -5576,6 +5590,7 @@ gdb_add_symbol_file(struct gnu_request *
+ 
+ 	req->name = lm->mod_namelist;
+ 	gdb_delete_symbol_file(req);
++	lm->loaded_objfile = NULL;
+ 
+ 	if ((lm->mod_flags & MOD_NOPATCH) == 0) {
+ 	        for (i = 0 ; i < lm->mod_sections; i++) {
+@@ -5623,12 +5638,15 @@ gdb_add_symbol_file(struct gnu_request *
+ 
+         ALL_OBJFILES(objfile) {
+ 		if (same_file(objfile->name, lm->mod_namelist)) {
+-                        loaded_objfile = objfile;
++			if (objfile->separate_debug_objfile)
++				lm->loaded_objfile = objfile->separate_debug_objfile;
++			else
++				lm->loaded_objfile = objfile;
+ 			break;
+ 		}
+         }
+ 
+-	if (!loaded_objfile)
++	if (!lm->loaded_objfile)
+                 req->flags |= GNU_COMMAND_FAILED;
+ }
+ 
diff --git a/symbols.c b/symbols.c
index 8a4c878..0d85ff7 100644
--- a/symbols.c
+++ b/symbols.c
@@ -3284,6 +3284,8 @@ dump_symbol_table(void)
 				lm->mod_section_data[s].size);
 		}
 
+		fprintf(fp, "        loaded_objfile: %lx\n", (ulong)lm->loaded_objfile);
+
 		if (CRASHDEBUG(1)) {
         		for (sp = lm->mod_load_symtable; 
 			     sp < lm->mod_load_symend; sp++) {
@@ -4100,6 +4102,7 @@ get_line_number(ulong addr, char *buf, int reserved)
 	struct load_module *lm;
 
 	buf[0] = NULLCHAR;
+	lm = NULL;
 
 	if (NO_LINE_NUMBERS() || !is_kernel_text(addr))
 		return(buf);
@@ -4129,6 +4132,8 @@ get_line_number(ulong addr, char *buf, int reserved)
 		req->command = GNU_GET_LINE_NUMBER;
 		req->addr = addr;
 		req->buf = buf;
+		if (lm && lm->loaded_objfile)
+			req->lm = lm;
 		if ((sp = value_search(addr, NULL)))
 			req->name = sp->name;
 		gdb_interface(req);
@@ -12025,6 +12030,7 @@ delete_load_module(ulong base_addr)
 			if (lm->mod_section_data)
 				free(lm->mod_section_data);
 			lm->mod_section_data = (struct mod_section_data *)0;
+			lm->loaded_objfile = NULL;
 		}
 		st->flags &= ~LOAD_MODULE_SYMS;
 		return;
@@ -12061,6 +12067,7 @@ delete_load_module(ulong base_addr)
 			if (lm->mod_section_data)
 				free(lm->mod_section_data);
 			lm->mod_section_data = (struct mod_section_data *)0;
+			lm->loaded_objfile = NULL;
                 } else if (lm->mod_flags & MOD_LOAD_SYMS)
 			st->flags |= LOAD_MODULE_SYMS;
         }

commit c8178eca9c74f81a7f803a58d339635cc152e8d9
Author: Dave Anderson <anderson@redhat.com>
Date:   Thu Nov 9 11:39:05 2017 -0500

    Update for support of Linux 4.14 and later PPC64 kernels where the
    hash page table geometry accomodates a larger virtual address range.
    Without the patch, the virtual-to-physical translation of user space
    virtual addresses by "vm -p", "vtop", and "rd -u" may generate an
    invalid translation or otherwise fail.
    (hbathini@linux.vnet.ibm.com)

diff --git a/defs.h b/defs.h
index 18f36b3..9132075 100644
--- a/defs.h
+++ b/defs.h
@@ -3915,6 +3915,9 @@ struct efi_memory_desc_t {
 #define PGD_INDEX_SIZE_L4_64K_3_10  12
 #define PMD_INDEX_SIZE_L4_64K_4_6  5
 #define PUD_INDEX_SIZE_L4_64K_4_6  5
+#define PMD_INDEX_SIZE_L4_64K_4_12 10
+#define PUD_INDEX_SIZE_L4_64K_4_12 7
+#define PGD_INDEX_SIZE_L4_64K_4_12 8
 #define PTE_INDEX_SIZE_RADIX_64K  5
 #define PMD_INDEX_SIZE_RADIX_64K  9
 #define PUD_INDEX_SIZE_RADIX_64K  9
diff --git a/ppc64.c b/ppc64.c
index 84cec09..672ee60 100644
--- a/ppc64.c
+++ b/ppc64.c
@@ -447,10 +447,16 @@ ppc64_init(int when)
 				} else if (!(machdep->flags & BOOK3E) &&
 				    (THIS_KERNEL_VERSION >= LINUX(4,6,0))) {
 					m->l1_index_size = PTE_INDEX_SIZE_L4_64K_3_10;
-					m->l2_index_size = PMD_INDEX_SIZE_L4_64K_4_6;
-					m->l3_index_size = PUD_INDEX_SIZE_L4_64K_4_6;
-					m->l4_index_size = PGD_INDEX_SIZE_L4_64K_3_10;
 
+					if (THIS_KERNEL_VERSION >= LINUX(4,12,0)) {
+						m->l2_index_size = PMD_INDEX_SIZE_L4_64K_4_12;
+						m->l3_index_size = PUD_INDEX_SIZE_L4_64K_4_12;
+						m->l4_index_size = PGD_INDEX_SIZE_L4_64K_4_12;
+					} else {
+						m->l2_index_size = PMD_INDEX_SIZE_L4_64K_4_6;
+						m->l3_index_size = PUD_INDEX_SIZE_L4_64K_4_6;
+						m->l4_index_size = PGD_INDEX_SIZE_L4_64K_3_10;
+					}
 				} else if (THIS_KERNEL_VERSION >= LINUX(3,10,0)) {
 					m->l1_index_size = PTE_INDEX_SIZE_L4_64K_3_10;
 					m->l2_index_size = PMD_INDEX_SIZE_L4_64K_3_10;

commit 03a3e57b9ad849314e262cac37787604a9fe8362
Author: Dave Anderson <anderson@redhat.com>
Date:   Thu Nov 9 14:04:08 2017 -0500

    Implemented a new "runq -T" option that displays the time lag of each
    CPU relative to the most recent runqueue timestamp.
    (oleksandr@redhat.com)

diff --git a/help.c b/help.c
index fa01bfb..e017b03 100644
--- a/help.c
+++ b/help.c
@@ -2532,7 +2532,7 @@ NULL
 char *help_runq[] = {
 "runq",
 "run queue",
-"[-t] [-m] [-g] [-c cpu(s)]",
+"[-t] [-T] [-m] [-g] [-c cpu(s)]",
 "  With no argument, this command displays the tasks on the run queues",
 "  of each cpu.",
 " ",
@@ -2541,6 +2541,8 @@ char *help_runq[] = {
 "         whichever applies; following each cpu timestamp is the last_run or ",
 "         timestamp value of the active task on that cpu, whichever applies, ",
 "         along with the task identification.",
+"     -T  Display the time lag of each CPU relative to the most recent runqueue",
+"         timestamp.",
 "     -m  Display the amount of time that the active task on each cpu has been",
 "         running, expressed in a format consisting of days, hours, minutes, ",
 "         seconds and milliseconds.",
diff --git a/task.c b/task.c
index f2628b7..724532d 100644
--- a/task.c
+++ b/task.c
@@ -55,6 +55,7 @@ static long rq_idx(int);
 static long cpu_idx(int);
 static void dump_runq(void);
 static void dump_on_rq_timestamp(void);
+static void dump_on_rq_lag(void);
 static void dump_on_rq_milliseconds(void);
 static void dump_runqueues(void);
 static void dump_prio_array(int, ulong, char *);
@@ -8045,10 +8046,11 @@ cmd_runq(void)
 	ulong *cpus = NULL;
 	int sched_debug = 0;
 	int dump_timestamp_flag = 0;
+	int dump_lag_flag = 0;
 	int dump_task_group_flag = 0;
 	int dump_milliseconds_flag = 0;
 
-        while ((c = getopt(argcnt, args, "dtgmc:")) != EOF) {
+        while ((c = getopt(argcnt, args, "dtTgmc:")) != EOF) {
                 switch(c)
                 {
 		case 'd':
@@ -8057,6 +8059,9 @@ cmd_runq(void)
 		case 't':
 			dump_timestamp_flag = 1;
 			break;
+		case 'T':
+			dump_lag_flag = 1;
+			break;
 		case 'm':
 			dump_milliseconds_flag = 1;
 			break;
@@ -8092,6 +8097,8 @@ cmd_runq(void)
 
 	if (dump_timestamp_flag)
                 dump_on_rq_timestamp();
+	else if (dump_lag_flag)
+		dump_on_rq_lag();
 	else if (dump_milliseconds_flag)
                 dump_on_rq_milliseconds();
 	else if (sched_debug)
@@ -8177,6 +8184,90 @@ dump_on_rq_timestamp(void)
 }
 
 /*
+ * Runqueue timestamp struct for dump_on_rq_lag().
+ */
+struct runq_ts_info {
+	int cpu;
+	ulonglong ts;
+};
+
+/*
+ * Comparison function for dump_on_rq_lag().
+ * Sorts runqueue timestamps in a descending order.
+ */
+static int
+compare_runq_ts(const void *p1, const void *p2)
+{
+	const struct runq_ts_info *ts1 = p1;
+	const struct runq_ts_info *ts2 = p2;
+
+	if (ts1->ts > ts2->ts)
+		return -1;
+
+	if (ts1->ts < ts2->ts)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * Calculates integer log10
+ */
+static ulong
+__log10ul(ulong x)
+{
+	ulong ret = 1;
+
+	while (x > 9) {
+		ret++;
+		x /= 10;
+	}
+
+	return ret;
+}
+
+/*
+ * Displays relative CPU lag.
+ */
+static void
+dump_on_rq_lag(void)
+{
+	struct syment *rq_sp;
+	int cpu;
+	ulong runq;
+	ulonglong timestamp;
+	struct runq_ts_info runq_ts[kt->cpus];
+
+	if (!(rq_sp = per_cpu_symbol_search("per_cpu__runqueues")))
+		error(FATAL, "per-cpu runqueues do not exist\n");
+	if (INVALID_MEMBER(rq_timestamp))
+		option_not_supported('T');
+
+	for (cpu = 0; cpu < kt->cpus; cpu++) {
+		if ((kt->flags & SMP) && (kt->flags &PER_CPU_OFF))
+			runq = rq_sp->value + kt->__per_cpu_offset[cpu];
+		else
+			runq = rq_sp->value;
+
+		readmem(runq + OFFSET(rq_timestamp), KVADDR, &timestamp,
+				sizeof(ulonglong), "per-cpu rq timestamp",
+				FAULT_ON_ERROR);
+
+		runq_ts[cpu].cpu = cpu;
+		runq_ts[cpu].ts = timestamp;
+	}
+
+	qsort(runq_ts, (size_t)kt->cpus, sizeof(struct runq_ts_info), compare_runq_ts);
+
+	for (cpu = 0; cpu < kt->cpus; cpu++) {
+		fprintf(fp, "%sCPU %d: %.2lf secs\n",
+			space(2 + __log10ul(kt->cpus) - __log10ul(runq_ts[cpu].cpu)),
+			runq_ts[cpu].cpu,
+			((double)runq_ts[0].ts - (double)runq_ts[cpu].ts) / 1000000000.0);
+	}
+}
+
+/*
  *  Displays the runqueue and active task timestamps of each cpu.
  */
 static void