mirror of
				https://git.proxmox.com/git/mirror_zfs.git
				synced 2025-10-26 18:05:04 +03:00 
			
		
		
		
	Fix free memory calculation on v3.14+
Provide infrastructure to auto-configure to enum and API changes in the global page stats used for our free memory calculations. arc_free_memory has been broken since an API change in Linux v3.14: 2016-07-28 v4.8 599d0c95 mm, vmscan: move LRU lists to node 2016-07-28 v4.8 75ef7184 mm, vmstat: add infrastructure for per-node vmstats These commits moved some of global_page_state() into global_node_page_state(). The API change was particularly egregious as, instead of breaking the old code, it silently did the wrong thing and we continued using global_page_state() where we should have been using global_node_page_state(), thus indexing into the wrong array via NR_SLAB_RECLAIMABLE et al. There have been further API changes along the way: 2017-07-06 v4.13 385386cf mm: vmstat: move slab statistics from zone to node counters 2017-09-06 v4.14 c41f012a mm: rename global_page_state to global_zone_page_state ...and various (incomplete, as it turns out) attempts to accomodate these changes in ZoL: 2017-08-242209e409Linux 4.8+ compatibility fix for vm stats 2017-09-16787acae0Linux 3.14 compat: IO acct, global_page_state, etc 2017-09-19661907e6Linux 4.14 compat: IO acct, global_page_state, etc The config infrastructure provided here resolves these issues going back to the original API change in v3.14 and is robust against further Linux changes in this area. Reviewed-by: Giuseppe Di Natale <dinatale2@llnl.gov> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: George Melikov <mail@gmelikov.ru> Signed-off-by: Chris Dunlop <chris@onthe.net.au> Closes #7170
This commit is contained in:
		
							parent
							
								
									7088545d01
								
							
						
					
					
						commit
						e9a7729008
					
				
							
								
								
									
										109
									
								
								config/kernel-global_page_state.m4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								config/kernel-global_page_state.m4
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| dnl # | ||||
| dnl # 4.8 API change | ||||
| dnl # | ||||
| dnl # 75ef71840539 mm, vmstat: add infrastructure for per-node vmstats | ||||
| dnl # 599d0c954f91 mm, vmscan: move LRU lists to node | ||||
| dnl # | ||||
| AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_NODE_PAGE_STATE], [ | ||||
| 	AC_MSG_CHECKING([whether global_node_page_state() exists]) | ||||
| 	ZFS_LINUX_TRY_COMPILE([ | ||||
| 		#include <linux/mm.h> | ||||
| 		#include <linux/vmstat.h> | ||||
| 	],[ | ||||
| 		(void) global_node_page_state(0); | ||||
| 	],[ | ||||
| 		AC_MSG_RESULT(yes) | ||||
| 		AC_DEFINE(ZFS_GLOBAL_NODE_PAGE_STATE, 1, [global_node_page_state() exists]) | ||||
| 	],[ | ||||
| 		AC_MSG_RESULT(no) | ||||
| 	]) | ||||
| ]) | ||||
| 
 | ||||
| dnl # | ||||
| dnl # 4.14 API change | ||||
| dnl # | ||||
| dnl # c41f012ade0b mm: rename global_page_state to global_zone_page_state | ||||
| dnl # | ||||
| AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE], [ | ||||
| 	AC_MSG_CHECKING([whether global_zone_page_state() exists]) | ||||
| 	ZFS_LINUX_TRY_COMPILE([ | ||||
| 		#include <linux/mm.h> | ||||
| 		#include <linux/vmstat.h> | ||||
| 	],[ | ||||
| 		(void) global_zone_page_state(0); | ||||
| 	],[ | ||||
| 		AC_MSG_RESULT(yes) | ||||
| 		AC_DEFINE(ZFS_GLOBAL_ZONE_PAGE_STATE, 1, [global_zone_page_state() exists]) | ||||
| 	],[ | ||||
| 		AC_MSG_RESULT(no) | ||||
| 	]) | ||||
| ]) | ||||
| 
 | ||||
| dnl # | ||||
| dnl # Create a define and autoconf variable for an enum member | ||||
| dnl # | ||||
| AC_DEFUN([ZFS_AC_KERNEL_ENUM_MEMBER], [ | ||||
| 	AC_MSG_CHECKING([whether enum $2 contains $1]) | ||||
| 	AS_IF([AC_TRY_COMMAND("${srcdir}/scripts/enum-extract.pl" "$2" "$3" | egrep -qx $1)],[ | ||||
| 		AC_MSG_RESULT([yes]) | ||||
| 		AC_DEFINE(m4_join([_], [ZFS_ENUM], m4_toupper($2), $1), 1, [enum $2 contains $1]) | ||||
| 		m4_join([_], [ZFS_ENUM], m4_toupper($2), $1)=1 | ||||
| 	],[ | ||||
| 		AC_MSG_RESULT([no]) | ||||
| 	]) | ||||
| ]) | ||||
| 
 | ||||
| dnl # | ||||
| dnl # Sanity check helpers | ||||
| dnl # | ||||
| AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_ERROR],[ | ||||
| 	AC_MSG_RESULT(no) | ||||
| 	AC_MSG_RESULT([$1 in either node_stat_item or zone_stat_item: $2]) | ||||
| 	AC_MSG_RESULT([configure needs updating, see: config/kernel-global_page_state.m4]) | ||||
| 	AC_MSG_FAILURE([SHUT 'ER DOWN CLANCY, SHE'S PUMPIN' MUD!]) | ||||
| ]) | ||||
| 
 | ||||
| AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK], [ | ||||
| 	enum_check_a="m4_join([_], [$ZFS_ENUM_NODE_STAT_ITEM], $1)" | ||||
| 	enum_check_b="m4_join([_], [$ZFS_ENUM_ZONE_STAT_ITEM], $1)" | ||||
| 	AS_IF([test -n "$enum_check_a" -a -n "$enum_check_b"],[ | ||||
| 		ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_ERROR([$1], [DUPLICATE]) | ||||
| 	]) | ||||
| 	AS_IF([test -z "$enum_check_a" -a -z "$enum_check_b"],[ | ||||
| 		ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_ERROR([$1], [NOT FOUND]) | ||||
| 	]) | ||||
| ]) | ||||
| 
 | ||||
| dnl # | ||||
| dnl # Ensure the config tests are finding one and only one of each enum of interest | ||||
| dnl # | ||||
| AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE_SANITY], [ | ||||
| 	AC_MSG_CHECKING([global_page_state enums are sane]) | ||||
| 
 | ||||
| 	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_FILE_PAGES]) | ||||
| 	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_INACTIVE_ANON]) | ||||
| 	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_INACTIVE_FILE]) | ||||
| 	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE_ENUM_CHECK([NR_SLAB_RECLAIMABLE]) | ||||
| 
 | ||||
| 	AC_MSG_RESULT(yes) | ||||
| ]) | ||||
| 
 | ||||
| dnl # | ||||
| dnl # enum members in which we're interested | ||||
| dnl # | ||||
| AC_DEFUN([ZFS_AC_KERNEL_GLOBAL_PAGE_STATE], [ | ||||
| 	ZFS_AC_KERNEL_GLOBAL_NODE_PAGE_STATE | ||||
| 	ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE | ||||
| 
 | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_FILE_PAGES],		[node_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_ANON],		[node_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_FILE],		[node_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_SLAB_RECLAIMABLE],	[node_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 
 | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_FILE_PAGES],		[zone_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_ANON],		[zone_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_INACTIVE_FILE],		[zone_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 	ZFS_AC_KERNEL_ENUM_MEMBER([NR_SLAB_RECLAIMABLE],	[zone_stat_item], [$LINUX/include/linux/mmzone.h]) | ||||
| 
 | ||||
| 	ZFS_AC_KERNEL_GLOBAL_ZONE_PAGE_STATE_SANITY | ||||
| ]) | ||||
| @ -1,22 +0,0 @@ | ||||
| dnl # | ||||
| dnl # 4.8 API change | ||||
| dnl # kernel vm counters change | ||||
| dnl # | ||||
| AC_DEFUN([ZFS_AC_KERNEL_VM_NODE_STAT], [ | ||||
| 	AC_MSG_CHECKING([whether to use vm_node_stat based fn's]) | ||||
| 	ZFS_LINUX_TRY_COMPILE([ | ||||
| 		#include <linux/mm.h> | ||||
| 		#include <linux/vmstat.h> | ||||
| 	],[ | ||||
| 			int a __attribute__ ((unused)) = NR_VM_NODE_STAT_ITEMS; | ||||
| 			long x __attribute__ ((unused)) = | ||||
| 				atomic_long_read(&vm_node_stat[0]); | ||||
| 			(void) global_node_page_state(0); | ||||
| 	],[ | ||||
| 		AC_MSG_RESULT(yes) | ||||
| 		AC_DEFINE(ZFS_GLOBAL_NODE_PAGE_STATE, 1, | ||||
| 			[using global_node_page_state()]) | ||||
| 	],[ | ||||
| 		AC_MSG_RESULT(no) | ||||
| 	]) | ||||
| ]) | ||||
| @ -123,7 +123,7 @@ AC_DEFUN([ZFS_AC_CONFIG_KERNEL], [ | ||||
| 	ZFS_AC_KERNEL_RENAME_WANTS_FLAGS | ||||
| 	ZFS_AC_KERNEL_HAVE_GENERIC_SETXATTR | ||||
| 	ZFS_AC_KERNEL_CURRENT_TIME | ||||
| 	ZFS_AC_KERNEL_VM_NODE_STAT | ||||
| 	ZFS_AC_KERNEL_GLOBAL_PAGE_STATE | ||||
| 	ZFS_AC_KERNEL_ACL_HAS_REFCOUNT | ||||
| 
 | ||||
| 	AS_IF([test "$LINUX_OBJ" != "$LINUX"], [ | ||||
|  | ||||
| @ -9,7 +9,8 @@ KERNEL_H = \ | ||||
| 	$(top_srcdir)/include/linux/kmap_compat.h \
 | ||||
| 	$(top_srcdir)/include/linux/simd_x86.h \
 | ||||
| 	$(top_srcdir)/include/linux/simd_aarch64.h \
 | ||||
| 	$(top_srcdir)/include/linux/mod_compat.h | ||||
| 	$(top_srcdir)/include/linux/mod_compat.h \
 | ||||
| 	$(top_srcdir)/include/linux/page_compat.h | ||||
| 
 | ||||
| USER_H = | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										78
									
								
								include/linux/page_compat.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								include/linux/page_compat.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| #ifndef	_ZFS_PAGE_COMPAT_H | ||||
| #define	_ZFS_PAGE_COMPAT_H | ||||
| 
 | ||||
| /*
 | ||||
|  * We have various enum members moving between two separate enum types, | ||||
|  * and accessed by different functions at various times. Centralise the | ||||
|  * insanity. | ||||
|  * | ||||
|  * < v4.8: all enums in zone_stat_item, via global_page_state() | ||||
|  * v4.8: some enums moved to node_stat_item, global_node_page_state() introduced | ||||
|  * v4.13: some enums moved from zone_stat_item to node_state_item | ||||
|  * v4.14: global_page_state() rename to global_zone_page_state() | ||||
|  * | ||||
|  * The defines used here are created by config/kernel-global_page_state.m4 | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * Create our own accessor functions to follow the Linux API changes | ||||
|  */ | ||||
| #if	defined(ZFS_GLOBAL_ZONE_PAGE_STATE) | ||||
| 
 | ||||
| /* global_zone_page_state() introduced */ | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_FILE_PAGES) | ||||
| #define	nr_file_pages() global_node_page_state(NR_FILE_PAGES) | ||||
| #else | ||||
| #define	nr_file_pages() global_zone_page_state(NR_FILE_PAGES) | ||||
| #endif | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_ANON) | ||||
| #define	nr_inactive_anon_pages() global_node_page_state(NR_INACTIVE_ANON) | ||||
| #else | ||||
| #define	nr_inactive_anon_pages() global_zone_page_state(NR_INACTIVE_ANON) | ||||
| #endif | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_FILE) | ||||
| #define	nr_inactive_file_pages() global_node_page_state(NR_INACTIVE_FILE) | ||||
| #else | ||||
| #define	nr_inactive_file_pages() global_zone_page_state(NR_INACTIVE_FILE) | ||||
| #endif | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_SLAB_RECLAIMABLE) | ||||
| #define	nr_slab_reclaimable_pages() global_node_page_state(NR_SLAB_RECLAIMABLE) | ||||
| #else | ||||
| #define	nr_slab_reclaimable_pages() global_zone_page_state(NR_SLAB_RECLAIMABLE) | ||||
| #endif | ||||
| 
 | ||||
| #elif	defined(ZFS_GLOBAL_NODE_PAGE_STATE) | ||||
| 
 | ||||
| /* global_node_page_state() introduced */ | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_FILE_PAGES) | ||||
| #define	nr_file_pages() global_node_page_state(NR_FILE_PAGES) | ||||
| #else | ||||
| #define	nr_file_pages() global_page_state(NR_FILE_PAGES) | ||||
| #endif | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_ANON) | ||||
| #define	nr_inactive_anon_pages() global_node_page_state(NR_INACTIVE_ANON) | ||||
| #else | ||||
| #define	nr_inactive_anon_pages() global_page_state(NR_INACTIVE_ANON) | ||||
| #endif | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_INACTIVE_FILE) | ||||
| #define	nr_inactive_file_pages() global_node_page_state(NR_INACTIVE_FILE) | ||||
| #else | ||||
| #define	nr_inactive_file_pages() global_page_state(NR_INACTIVE_FILE) | ||||
| #endif | ||||
| #if	defined(ZFS_ENUM_NODE_STAT_ITEM_NR_SLAB_RECLAIMABLE) | ||||
| #define	nr_slab_reclaimable_pages() global_node_page_state(NR_SLAB_RECLAIMABLE) | ||||
| #else | ||||
| #define	nr_slab_reclaimable_pages() global_page_state(NR_SLAB_RECLAIMABLE) | ||||
| #endif | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
| /* global_page_state() only */ | ||||
| #define	nr_file_pages()			global_page_state(NR_FILE_PAGES) | ||||
| #define	nr_inactive_anon_pages()	global_page_state(NR_INACTIVE_ANON) | ||||
| #define	nr_inactive_file_pages()	global_page_state(NR_INACTIVE_FILE) | ||||
| #define	nr_slab_reclaimable_pages()	global_page_state(NR_SLAB_RECLAIMABLE) | ||||
| 
 | ||||
| #endif /* ZFS_GLOBAL_ZONE_PAGE_STATE */ | ||||
| 
 | ||||
| #endif /* _ZFS_PAGE_COMPAT_H */ | ||||
| @ -297,6 +297,7 @@ | ||||
| #include <sys/fs/swapnode.h> | ||||
| #include <sys/zpl.h> | ||||
| #include <linux/mm_compat.h> | ||||
| #include <linux/page_compat.h> | ||||
| #endif | ||||
| #include <sys/callb.h> | ||||
| #include <sys/kstat.h> | ||||
| @ -4699,17 +4700,11 @@ arc_free_memory(void) | ||||
| 	si_meminfo(&si); | ||||
| 	return (ptob(si.freeram - si.freehigh)); | ||||
| #else | ||||
| #ifdef ZFS_GLOBAL_NODE_PAGE_STATE | ||||
| 	return (ptob(nr_free_pages() + | ||||
| 	    global_node_page_state(NR_INACTIVE_FILE) + | ||||
| 	    global_node_page_state(NR_INACTIVE_ANON) + | ||||
| 	    global_node_page_state(NR_SLAB_RECLAIMABLE))); | ||||
| #else | ||||
| 	return (ptob(nr_free_pages() + | ||||
| 	    global_page_state(NR_INACTIVE_FILE) + | ||||
| 	    global_page_state(NR_INACTIVE_ANON) + | ||||
| 	    global_page_state(NR_SLAB_RECLAIMABLE))); | ||||
| #endif /* ZFS_GLOBAL_NODE_PAGE_STATE */ | ||||
| 	    nr_inactive_file_pages() + | ||||
| 	    nr_inactive_anon_pages() + | ||||
| 	    nr_slab_reclaimable_pages())); | ||||
| 
 | ||||
| #endif /* CONFIG_HIGHMEM */ | ||||
| #else | ||||
| 	return (spa_get_random(arc_all_memory() * 20 / 100)); | ||||
| @ -5121,13 +5116,7 @@ arc_evictable_memory(void) | ||||
| 	 * Scale reported evictable memory in proportion to page cache, cap | ||||
| 	 * at specified min/max. | ||||
| 	 */ | ||||
| #ifdef ZFS_GLOBAL_NODE_PAGE_STATE | ||||
| 	uint64_t min = (ptob(global_node_page_state(NR_FILE_PAGES)) / 100) * | ||||
| 	    zfs_arc_pc_percent; | ||||
| #else | ||||
| 	uint64_t min = (ptob(global_page_state(NR_FILE_PAGES)) / 100) * | ||||
| 	    zfs_arc_pc_percent; | ||||
| #endif | ||||
| 	uint64_t min = (ptob(nr_file_pages()) / 100) * zfs_arc_pc_percent; | ||||
| 	min = MAX(arc_c_min, MIN(arc_c_max, min)); | ||||
| 
 | ||||
| 	if (arc_dirty >= min) | ||||
|  | ||||
| @ -12,6 +12,7 @@ EXTRA_DIST = \ | ||||
| 	commitcheck.sh \
 | ||||
| 	dkms.mkconf \
 | ||||
| 	dkms.postbuild \
 | ||||
| 	enum-extract.pl \
 | ||||
| 	kmodtool \
 | ||||
| 	paxcheck.sh \
 | ||||
| 	zfs2zol-patch.sed \
 | ||||
|  | ||||
							
								
								
									
										58
									
								
								scripts/enum-extract.pl
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										58
									
								
								scripts/enum-extract.pl
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,58 @@ | ||||
| #!/usr/bin/perl -w | ||||
| 
 | ||||
| my $usage = <<EOT; | ||||
| usage: config-enum enum [file ...] | ||||
| 
 | ||||
| Returns the elements from an enum declaration. | ||||
| 
 | ||||
| "Best effort": we're not building an entire C interpreter here! | ||||
| EOT | ||||
| 
 | ||||
| use warnings; | ||||
| use strict; | ||||
| use Getopt::Std; | ||||
| 
 | ||||
| my %opts; | ||||
| 
 | ||||
| if (!getopts("", \%opts) || @ARGV < 1) { | ||||
| 	print $usage; | ||||
| 	exit 2; | ||||
| } | ||||
| 
 | ||||
| my $enum = shift; | ||||
| 
 | ||||
| my $in_enum = 0; | ||||
| 
 | ||||
| while (<>) { | ||||
| 	# comments | ||||
| 	s/\/\*.*\*\///; | ||||
| 	if (m/\/\*/) { | ||||
| 		while ($_ .= <>) { | ||||
| 			last if s/\/\*.*\*\///s; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	# preprocessor stuff | ||||
| 	next if /^#/; | ||||
| 
 | ||||
| 	# find our enum | ||||
| 	$in_enum = 1 if s/^\s*enum\s+${enum}(?:\s|$)//; | ||||
| 	next unless $in_enum; | ||||
| 
 | ||||
| 	# remove explicit values | ||||
| 	s/\s*=[^,]+,/,/g; | ||||
| 
 | ||||
| 	# extract each identifier | ||||
| 	while (m/\b([a-z_][a-z0-9_]*)\b/ig) { | ||||
| 		print $1, "\n"; | ||||
| 	} | ||||
| 
 | ||||
| 	# | ||||
| 	# don't exit: there may be multiple versions of the same enum, e.g. | ||||
| 	# inside different #ifdef blocks. Let's explicitly return all of | ||||
| 	# them and let external tooling deal with it. | ||||
| 	# | ||||
| 	$in_enum = 0 if m/}\s*;/; | ||||
| } | ||||
| 
 | ||||
| exit 0; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 chrisrd
						chrisrd