Skip to content

Commit

Permalink
Fix free memory calculation on v3.14+
Browse files Browse the repository at this point in the history
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-24 2209e40 Linux 4.8+ compatibility fix for vm stats
2017-09-16 787acae Linux 3.14 compat: IO acct, global_page_state, etc
2017-09-19 661907e Linux 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 <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: George Melikov <[email protected]>
Signed-off-by: Chris Dunlop <[email protected]>
Closes openzfs#7170
  • Loading branch information
chrisrd authored and tonyhutter committed Mar 8, 2018
1 parent 5767323 commit c82dc52
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 41 deletions.
109 changes: 109 additions & 0 deletions config/kernel-global_page_state.m4
Original file line number Diff line number Diff line change
@@ -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
])
22 changes: 0 additions & 22 deletions config/kernel-vm_node_stat.m4

This file was deleted.

2 changes: 1 addition & 1 deletion config/kernel.m4
Original file line number Diff line number Diff line change
Expand Up @@ -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"], [
Expand Down
3 changes: 2 additions & 1 deletion include/linux/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 =

Expand Down
78 changes: 78 additions & 0 deletions include/linux/page_compat.h
Original file line number Diff line number Diff line change
@@ -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 */
23 changes: 6 additions & 17 deletions module/zfs/arc.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,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>
Expand Down Expand Up @@ -4016,17 +4017,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));
Expand Down Expand Up @@ -4437,13 +4432,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)
Expand Down
2 changes: 2 additions & 0 deletions scripts/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ EXTRA_DIST = dkms.mkconf dkms.postbuild kmodtool zfs2zol-patch.sed cstyle.pl
pkgdatadir = $(datadir)/@PACKAGE@
dist_pkgdata_SCRIPTS = \
$(top_builddir)/scripts/common.sh \
$(top_srcdir)/scripts/enum-extract.pl \
$(top_srcdir)/scripts/zimport.sh \
$(top_srcdir)/scripts/zfs.sh \
$(top_srcdir)/scripts/zfs-tests.sh \
Expand All @@ -15,3 +16,4 @@ dist_pkgdata_SCRIPTS = \
$(top_srcdir)/scripts/zpios-survey.sh \
$(top_srcdir)/scripts/smb.sh \
$(top_srcdir)/scripts/zfs-helpers.sh

58 changes: 58 additions & 0 deletions scripts/enum-extract.pl
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit c82dc52

Please sign in to comment.