diff --git a/.gitignore b/.gitignore
index 9735bee..8f9b96c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-SOURCES/tuned-2.15.0.tar.gz
+SOURCES/tuned-2.16.0.tar.gz
diff --git a/.tuned.metadata b/.tuned.metadata
index f880b6a..2d7f88a 100644
--- a/.tuned.metadata
+++ b/.tuned.metadata
@@ -1 +1 @@
-bfb3def0b687bbdae2b3e191d2fda46b3ffca1c0 SOURCES/tuned-2.15.0.tar.gz
+e20fcfb734f869fb175cb88dc7ef6e5eb3cd5946 SOURCES/tuned-2.16.0.tar.gz
diff --git a/SOURCES/tuned-2.15.0-mssql-fix.patch b/SOURCES/tuned-2.15.0-mssql-fix.patch
deleted file mode 100644
index 5804de8..0000000
--- a/SOURCES/tuned-2.15.0-mssql-fix.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 05739d0719e3fc8a1cb0954f0b410e429be5d7f1 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= <jskarvad@redhat.com>
-Date: Wed, 7 Apr 2021 19:55:09 +0200
-Subject: [PATCH] mssql: update profile to be in sync with MS recommendations
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Resolves: rhbz#1942733
-
-Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
----
- profiles/mssql/tuned.conf | 17 +++++++++++++++--
- 1 file changed, 15 insertions(+), 2 deletions(-)
-
-diff --git a/profiles/mssql/tuned.conf b/profiles/mssql/tuned.conf
-index 35e208a8..1213a0cb 100644
---- a/profiles/mssql/tuned.conf
-+++ b/profiles/mssql/tuned.conf
-@@ -6,11 +6,24 @@
- summary=Optimize for MS SQL Server
- include=throughput-performance
- 
-+[cpu]
-+force_latency=5
-+
- [vm]
--transparent_hugepage.defrag=always
-+# For multi-instance SQL deployments use 'madvise' instead of 'always'
-+transparent_hugepages=always
- 
- [sysctl]
--vm.max_map_count=800000
-+vm.swappiness=1
-+vm.dirty_background_ratio=3
-+vm.dirty_ratio=80
-+vm.dirty_expire_centisecs=500
-+vm.dirty_writeback_centisecs=100
-+vm.max_map_count=1600000
-+net.core.rmem_default=262144
-+net.core.rmem_max=4194304
-+net.core.wmem_default=262144
-+net.core.wmem_max=1048576
- kernel.numa_balancing=0
- kernel.sched_latency_ns=60000000
- kernel.sched_min_granularity_ns=15000000
diff --git a/SOURCES/tuned-2.15.0-netdev-queue-count.patch b/SOURCES/tuned-2.15.0-netdev-queue-count.patch
deleted file mode 100644
index 09e31d7..0000000
--- a/SOURCES/tuned-2.15.0-netdev-queue-count.patch
+++ /dev/null
@@ -1,168 +0,0 @@
-diff --git a/profiles/realtime/realtime-variables.conf b/profiles/realtime/realtime-variables.conf
-index c2da595..9d929e0 100644
---- a/profiles/realtime/realtime-variables.conf
-+++ b/profiles/realtime/realtime-variables.conf
-@@ -9,3 +9,11 @@
- # kernel supports it.
- #
- # isolate_managed_irq=Y
-+#
-+#
-+# Set the desired combined queue count value using the parameter provided
-+# below. Ideally this should be set to the number of housekeeping CPUs i.e.,
-+# in the example given below it is assumed that the system has 4 housekeeping
-+# (non-isolated) CPUs.
-+#
-+# netdev_queue_count=4
-diff --git a/profiles/realtime/tuned.conf b/profiles/realtime/tuned.conf
-index 8eed36e..2400849 100644
---- a/profiles/realtime/tuned.conf
-+++ b/profiles/realtime/tuned.conf
-@@ -35,6 +35,9 @@ assert2=${f:assertion:isolated_cores contains online CPU(s):${isolated_cores_exp
- isolate_managed_irq = ${isolate_managed_irq}
- managed_irq=${f:regex_search_ternary:${isolate_managed_irq}:\b[y,Y,1,t,T]\b:managed_irq,domain,:}
- 
-+[net]
-+channels=combined ${f:check_net_queue_count:${netdev_queue_count}}
-+
- [sysctl]
- kernel.hung_task_timeout_secs = 600
- kernel.nmi_watchdog = 0
-diff --git a/tuned/profiles/functions/function_check_net_queue_count.py b/tuned/profiles/functions/function_check_net_queue_count.py
-new file mode 100644
-index 0000000..eb54f98
---- /dev/null
-+++ b/tuned/profiles/functions/function_check_net_queue_count.py
-@@ -0,0 +1,22 @@
-+import tuned.logs
-+from . import base
-+
-+log = tuned.logs.get()
-+
-+class check_net_queue_count(base.Function):
-+	"""
-+	Checks whether the user has specified a queue count for net devices. If
-+        not, return the number of housekeeping CPUs.
-+	"""
-+	def __init__(self):
-+		# 1 argument
-+		super(check_net_queue_count, self).__init__("check_net_queue_count", 1, 1)
-+
-+	def execute(self, args):
-+		if not super(check_net_queue_count, self).execute(args):
-+			return None
-+		if args[0].isdigit():
-+			return args[0]
-+		(ret, out) = self._cmd.execute(["nproc"])
-+		log.warn("net-dev queue count is not correctly specified, setting it to HK CPUs %s" % (out))
-+		return out
-diff --git a/tuned/plugins/plugin_net.py b/tuned/plugins/plugin_net.py
-index 4d4c19e..a20d87e 100644
---- a/tuned/plugins/plugin_net.py
-+++ b/tuned/plugins/plugin_net.py
-@@ -122,6 +122,13 @@ class NetTuningPlugin(base.Plugin):
- 			"rx-jumbo": None,
- 			"tx": None }
- 
-+	@classmethod
-+	def _get_config_options_channels(cls):
-+		return { "rx": None,
-+			"tx": None,
-+			"other": None,
-+			"combined": None }
-+
- 	@classmethod
- 	def _get_config_options(cls):
- 		return {
-@@ -132,6 +139,7 @@ class NetTuningPlugin(base.Plugin):
- 			"coalesce": None,
- 			"pause": None,
- 			"ring": None,
-+			"channels": None,
- 		}
- 
- 	def _init_stats_and_idle(self, instance, device):
-@@ -282,7 +290,8 @@ class NetTuningPlugin(base.Plugin):
- 		params = set(d.keys())
- 		supported_getter = { "coalesce": self._get_config_options_coalesce, \
- 				"pause": self._get_config_options_pause, \
--				"ring": self._get_config_options_ring }
-+				"ring": self._get_config_options_ring, \
-+				"channels": self._get_config_options_channels }
- 		supported = set(supported_getter[context]().keys())
- 		if not params.issubset(supported):
- 			log.error("unknown %s parameter(s): %s" % (context, str(params - supported)))
-@@ -313,6 +322,29 @@ class NetTuningPlugin(base.Plugin):
- 		l = [x for x in [re.split(r":\s*", x) for x in l] if len(x) == 2]
- 		return dict(l)
- 
-+	# parse output of ethtool -l
-+	def _parse_channels_parameters(self, s):
-+		a = re.split(r"^Current hardware settings:$", s, flags=re.MULTILINE)
-+		s = a[1]
-+		s = self._cmd.multiple_re_replace(\
-+				{"RX": "rx",
-+				"TX": "tx",
-+				"Other": "other",
-+				"Combined": "combined"}, s)
-+		l = s.split("\n")
-+		l = [x for x in l if x != '']
-+		l = [x for x in [re.split(r":\s*", x) for x in l] if len(x) == 2]
-+		return dict(l)
-+
-+	def _replace_channels_parameters(self, context, params_list, dev_params):
-+		mod_params_list = []
-+		if "combined" in params_list:
-+			mod_params_list.extend(["rx", params_list[1], "tx", params_list[1]])
-+		else:
-+			cnt = str(max(int(params_list[1]), int(params_list[3])))
-+			mod_params_list.extend(["combined", cnt])
-+		return dict(list(zip(mod_params_list[::2], mod_params_list[1::2])))
-+
- 	def _check_device_support(self, context, parameters, device, dev_params):
- 		"""Filter unsupported parameters and log warnings about it
- 
-@@ -337,7 +369,8 @@ class NetTuningPlugin(base.Plugin):
- 			parameters.pop(param, None)
- 
- 	def _get_device_parameters(self, context, device):
--		context2opt = { "coalesce": "-c", "features": "-k", "pause": "-a", "ring": "-g" }
-+		context2opt = { "coalesce": "-c", "features": "-k", "pause": "-a", "ring": "-g", \
-+				"channels": "-l"}
- 		opt = context2opt[context]
- 		ret, value = self._cmd.execute(["ethtool", opt, device])
- 		if ret != 0 or len(value) == 0:
-@@ -345,7 +378,8 @@ class NetTuningPlugin(base.Plugin):
- 		context2parser = { "coalesce": self._parse_device_parameters, \
- 				"features": self._parse_device_parameters, \
- 				"pause": self._parse_pause_parameters, \
--				"ring": self._parse_ring_parameters }
-+				"ring": self._parse_ring_parameters, \
-+				"channels": self._parse_channels_parameters }
- 		parser = context2parser[context]
- 		d = parser(value)
- 		if context == "coalesce" and not self._check_parameters(context, d):
-@@ -362,10 +396,14 @@ class NetTuningPlugin(base.Plugin):
- 		# check if device supports parameters and filter out unsupported ones
- 		if dev_params:
- 			self._check_device_support(context, d, device, dev_params)
-+			# replace the channel parameters based on the device support
-+			if context == "channels" and int(dev_params[next(iter(d))]) == 0:
-+				d = self._replace_channels_parameters(context, self._cmd.dict2list(d), dev_params)
- 
- 		if not sim and len(d) != 0:
- 			log.debug("setting %s: %s" % (context, str(d)))
--			context2opt = { "coalesce": "-C", "features": "-K", "pause": "-A", "ring": "-G" }
-+			context2opt = { "coalesce": "-C", "features": "-K", "pause": "-A", "ring": "-G", \
-+                                "channels": "-L"}
- 			opt = context2opt[context]
- 			# ignore ethtool return code 80, it means parameter is already set
- 			self._cmd.execute(["ethtool", opt, device] + self._cmd.dict2list(d), no_errors = [80])
-@@ -422,3 +460,7 @@ class NetTuningPlugin(base.Plugin):
- 	@command_custom("ring", per_device = True)
- 	def _ring(self, start, value, device, verify, ignore_missing):
- 		return self._custom_parameters("ring", start, value, device, verify)
-+
-+	@command_custom("channels", per_device = True)
-+	def _channels(self, start, value, device, verify, ignore_missing):
-+		return self._custom_parameters("channels", start, value, device, verify)
diff --git a/SOURCES/tuned-2.16.0-rhel-8-profiles.patch b/SOURCES/tuned-2.16.0-rhel-8-profiles.patch
new file mode 100644
index 0000000..e357432
--- /dev/null
+++ b/SOURCES/tuned-2.16.0-rhel-8-profiles.patch
@@ -0,0 +1,83 @@
+diff --git a/profiles/latency-performance/tuned.conf b/profiles/latency-performance/tuned.conf
+index da1e357..66f06ae 100644
+--- a/profiles/latency-performance/tuned.conf
++++ b/profiles/latency-performance/tuned.conf
+@@ -32,3 +32,16 @@ vm.dirty_background_ratio=3
+ # 100 tells the kernel to aggressively swap processes out of physical memory
+ # and move them to swap cache
+ vm.swappiness=10
++
++[scheduler]
++# ktune sysctl settings for rhel6 servers, maximizing i/o throughput
++#
++# Minimal preemption granularity for CPU-bound tasks:
++# (default: 1 msec#  (1 + ilog(ncpus)), units: nanoseconds)
++sched_min_granularity_ns = 3000000
++sched_wakeup_granularity_ns = 4000000
++
++# The total time the scheduler will consider a migrated process
++# "cache hot" and thus less likely to be re-migrated
++# (system default is 500000, i.e. 0.5 ms)
++sched_migration_cost_ns = 5000000
+diff --git a/profiles/sap-hana/tuned.conf b/profiles/sap-hana/tuned.conf
+index 81d5930..c91a9ee 100644
+--- a/profiles/sap-hana/tuned.conf
++++ b/profiles/sap-hana/tuned.conf
+@@ -20,3 +20,7 @@ kernel.numa_balancing = 0
+ vm.dirty_ratio = 40
+ vm.dirty_background_ratio = 10
+ vm.swappiness = 10
++
++[scheduler]
++sched_min_granularity_ns = 3000000
++sched_wakeup_granularity_ns = 4000000
+diff --git a/profiles/throughput-performance/tuned.conf b/profiles/throughput-performance/tuned.conf
+index 98c6b26..ebb3f7d 100644
+--- a/profiles/throughput-performance/tuned.conf
++++ b/profiles/throughput-performance/tuned.conf
+@@ -58,9 +58,31 @@ vm.dirty_background_ratio = 10
+ # and move them to swap cache
+ vm.swappiness=10
+ 
++[scheduler]
++# ktune sysctl settings for rhel6 servers, maximizing i/o throughput
++#
++# Minimal preemption granularity for CPU-bound tasks:
++# (default: 1 msec#  (1 + ilog(ncpus)), units: nanoseconds)
++sched_min_granularity_ns = 10000000
++
++# SCHED_OTHER wake-up granularity.
++# (default: 1 msec#  (1 + ilog(ncpus)), units: nanoseconds)
++#
++# This option delays the preemption effects of decoupled workloads
++# and reduces their over-scheduling. Synchronous workloads will still
++# have immediate wakeup/sleep latencies.
++sched_wakeup_granularity_ns = 15000000
++
+ # Marvell ThunderX
+ [sysctl.thunderx]
+ type=sysctl
+ uname_regex=aarch64
+ cpuinfo_regex=${thunderx_cpuinfo_regex}
+ kernel.numa_balancing=0
++
++# AMD
++[scheduler.amd]
++type=scheduler
++uname_regex=x86_64
++cpuinfo_regex=${amd_cpuinfo_regex}
++sched_migration_cost_ns=5000000
+diff --git a/profiles/virtual-host/tuned.conf b/profiles/virtual-host/tuned.conf
+index c1942da..3358105 100644
+--- a/profiles/virtual-host/tuned.conf
++++ b/profiles/virtual-host/tuned.conf
+@@ -14,3 +14,9 @@ vm.dirty_background_ratio = 5
+ [cpu]
+ # Setting C3 state sleep mode/power savings
+ force_latency=cstate.id:3|70
++
++[scheduler]
++# The total time the scheduler will consider a migrated process
++# "cache hot" and thus less likely to be re-migrated
++# (system default is 500000, i.e. 0.5 ms)
++sched_migration_cost_ns = 5000000
diff --git a/SPECS/tuned.spec b/SPECS/tuned.spec
index 7c552a3..df515ab 100644
--- a/SPECS/tuned.spec
+++ b/SPECS/tuned.spec
@@ -33,8 +33,8 @@
 
 Summary: A dynamic adaptive system tuning daemon
 Name: tuned
-Version: 2.15.0
-Release: 2%{?prerel1}%{?dist}.1
+Version: 2.16.0
+Release: 1%{?prerel1}%{?dist}
 License: GPLv2+
 Source0: https://github.com/redhat-performance/%{name}/archive/v%{version}%{?prerel2}/%{name}-%{version}%{?prerel2}.tar.gz
 # RHEL-8 specific recommend.conf:
@@ -45,12 +45,13 @@ BuildRequires: systemd, desktop-file-utils
 Requires(post): systemd, virt-what
 Requires(preun): systemd
 Requires(postun): systemd
+BuildRequires: make
 BuildRequires: %{_py}, %{_py}-devel
 # BuildRequires for 'make test'
 BuildRequires: %{_py}-unittest2, %{_py}-configobj, %{_py}-mock
-BuildRequires: %{_py}-decorator, %{_py}-pyudev
-Requires: %{_py}-decorator, %{_py}-pyudev, %{_py}-configobj
-Requires: %{_py}-schedutils, %{_py}-linux-procfs, %{_py}-perf
+BuildRequires: %{_py}-pyudev
+Requires: %{_py}-pyudev, %{_py}-configobj
+Requires: %{_py}-linux-procfs, %{_py}-perf
 # requires for packages with inconsistent python2/3 names
 %if %{with python3}
 # BuildRequires for 'make test'
@@ -72,11 +73,16 @@ Recommends: hdparm
 Recommends: kernel-tools
 Recommends: kmod
 %endif
+# syspurpose
+%if 0%{?rhel} > 8
+Requires: subscription-manager
+%else
 %if 0%{?rhel} > 7
 Requires: python3-syspurpose
 %endif
-Patch0: tuned-2.15.0-netdev-queue-count.patch
-Patch1: tuned-2.15.0-mssql-fix.patch
+%endif
+# Revert upstream profiles changes which have not been approved for RHEL-8 (yet)
+Patch0: tuned-2.16.0-rhel-8-profiles.patch
 
 %description
 The tuned package contains a daemon that tunes system settings dynamically.
@@ -232,7 +238,6 @@ Additional tuned profile(s) targeted to PostgreSQL server loads.
 %prep
 %setup -q -n %{name}-%{version}%{?prerel2}
 %patch0 -p1
-%patch1 -p1
 
 # Replace the upstream recommend.conf with a RHEL-8-specific one
 rm -f recommend.conf
@@ -514,13 +519,47 @@ fi
 %{_mandir}/man7/tuned-profiles-postgresql.7*
 
 %changelog
-* Wed Apr 21 2021 Jaroslav Škarvada <jskarvad@redhat.com> - 2.15.0-2.1
+* Wed Jul 21 2021 Jaroslav Škarvada <jskarvad@redhat.com> - 2.16.0-1
+- new release
+  - rebased tuned to latest upstream
+    related: rhbz#1936426
+
+* Wed Jul  7 2021 Jaroslav Škarvada <jskarvad@redhat.com> - 2.16.0-0.1.rc1
+- new release
+  - rebased tuned to latest upstream
+    resolves: rhbz#1936426
+  - realtime: "isolate_managed_irq=Y" should be mentioned in
+    "/etc/tuned/realtime-virtual-*-variables.conf"
+    resolves: rhbz#1817827
+  - realtime: changed tuned default to "isolcpus=domain,managed_irq,X-Y"
+    resolves: rhbz#1820626
+  - applying a profile with multiple inheritance where parents include a common
+    ancestor fails
+    resolves: rhbz#1825882
+  - failure in moving i40e IRQ threads to housekeeping CPUs from isolated CPUs
+    resolves: rhbz#1933069
+  - sort network devices before matching by regex
+    resolves: rhbz#1939970
+  - net: fixed traceback while adjusting the netdev queue count
+    resolves: rhbz#1943291
+  - net: fixed traceback if the first listed device returns netlink error
+    resolves: rhbz#1944686
+  - realtime: improve verification
+    resolves: rhbz#1947858
+  - bootloader: add support for the rpm-ostree
+    resolves: rhbz#1950164
+  - net: fixed traceback if a device channel contains n/a
+    resolves: rhbz#1974071
+  - mssql: updated the profile
+    resolves: rhbz#1942733
+
+* Wed Apr 21 2021 Jaroslav Škarvada <jskarvad@redhat.com> - 2.15.0-3
 - updated mssql profile
-  resolves: rhbz#1945617
+  resolves: rhbz#1942733
 
 * Fri Feb 19 2021 Jaroslav Škarvada <jskarvad@redhat.com> - 2.15.0-2
 - realtime: added support for netdev_queue_count and extended plugin_net
-  resolves: rhbz#1846767
+  resolves: rhbz#1951992
 
 * Thu Dec 17 2020 Jaroslav Škarvada <jskarvad@redhat.com> - 2.15.0-1
 - new release