diff --git a/include/linux/vmevent.h b/include/linux/vmevent.h
index b1c4016..1397ade 100644
--- a/include/linux/vmevent.h
+++ b/include/linux/vmevent.h
@@ -10,6 +10,7 @@ enum {
 	VMEVENT_ATTR_NR_AVAIL_PAGES	= 1UL,
 	VMEVENT_ATTR_NR_FREE_PAGES	= 2UL,
 	VMEVENT_ATTR_NR_SWAP_PAGES	= 3UL,
+	VMEVENT_ATTR_PRESSURE		= 4UL,
 
 	VMEVENT_ATTR_MAX		/* non-ABI */
 };
@@ -46,6 +47,11 @@ struct vmevent_attr {
 	__u64			value;
 
 	/*
+	 * Some attributes accept two configuration values.
+	 */
+	__u64			value2;
+
+	/*
 	 * Type of profiled attribute from VMEVENT_ATTR_XXX
 	 */
 	__u32			type;
@@ -97,4 +103,34 @@ struct vmevent_event {
 	struct vmevent_attr	attrs[];
 };
 
+#ifdef __KERNEL__
+
+struct mem_cgroup;
+
+extern void __vmevent_pressure(struct mem_cgroup *memcg,
+			       ulong scanned,
+			       ulong reclaimed);
+
+static inline void vmevent_pressure(struct mem_cgroup *memcg,
+				    ulong scanned,
+				    ulong reclaimed)
+{
+	if (!scanned)
+		return;
+
+	if (IS_BUILTIN(CONFIG_MEMCG) && memcg) {
+		/*
+		 * The vmevent API reports system pressure, for per-cgroup
+		 * pressure, we'll chain cgroups notifications, this is to
+		 * be implemented.
+		 *
+		 * memcg_vm_pressure(target_mem_cgroup, scanned, reclaimed);
+		 */
+		return;
+	}
+	__vmevent_pressure(memcg, scanned, reclaimed);
+}
+
+#endif
+
 #endif /* _LINUX_VMEVENT_H */
diff --git a/mm/vmevent.c b/mm/vmevent.c
index d643615..12d0131 100644
--- a/mm/vmevent.c
+++ b/mm/vmevent.c
@@ -4,6 +4,7 @@
 #include <linux/vmevent.h>
 #include <linux/syscalls.h>
 #include <linux/workqueue.h>
+#include <linux/interrupt.h>
 #include <linux/file.h>
 #include <linux/list.h>
 #include <linux/poll.h>
@@ -30,6 +31,25 @@ struct vmevent_watch {
 	wait_queue_head_t		waitq;
 };
 
+struct vmevent_pwatcher {
+	struct vmevent_watch *watch;
+	struct vmevent_attr *attr;
+	struct vmevent_attr *samp;
+	struct list_head node;
+
+	uint scanned;
+	uint reclaimed;
+	uint window;
+};
+
+static LIST_HEAD(vmevent_pwatchers);
+static DEFINE_SPINLOCK(vmevent_pwatchers_lock);
+
+static uint vmevent_scanned;
+static uint vmevent_reclaimed;
+static uint vmevent_minwin = UINT_MAX; /* Smallest window in the list. */
+static DEFINE_SPINLOCK(vmevent_pressure_lock);
+
 typedef u64 (*vmevent_attr_sample_fn)(struct vmevent_watch *watch,
 				      struct vmevent_attr *attr);
 
@@ -141,6 +161,10 @@ static bool vmevent_match(struct vmevent_watch *watch)
 		struct vmevent_attr *samp = &watch->sample_attrs[i];
 		u64 val;
 
+		/* Pressure is event-driven, not polled */
+		if (attr->type == VMEVENT_ATTR_PRESSURE)
+			continue;
+
 		val = vmevent_sample_attr(watch, attr);
 		if (!ret && vmevent_match_attr(attr, val))
 			ret = 1;
@@ -204,6 +228,94 @@ static void vmevent_start_timer(struct vmevent_watch *watch)
 	vmevent_schedule_watch(watch);
 }
 
+static ulong vmevent_calc_pressure(struct vmevent_pwatcher *pw)
+{
+	uint win = pw->window;
+	uint s = pw->scanned;
+	uint r = pw->reclaimed;
+	ulong p;
+
+	/*
+	 * We calculate the ratio (in percents) of how many pages were
+	 * scanned vs. reclaimed in a given time frame (window). Note that
+	 * time is in VM reclaimer's "ticks", i.e. number of pages
+	 * scanned. This makes it possible set desired reaction time and
+	 * serves as a ratelimit.
+	 */
+	p = win - (r * win / s);
+	p = p * 100 / win;
+
+	pr_debug("%s: %3lu  (s: %6u  r: %6u)\n", __func__, p, s, r);
+
+	return p;
+}
+
+static void vmevent_match_pressure(struct vmevent_pwatcher *pw)
+{
+	struct vmevent_watch *watch = pw->watch;
+	struct vmevent_attr *attr = pw->attr;
+	ulong val;
+
+	val = vmevent_calc_pressure(pw);
+
+	/* Next round. */
+	pw->scanned = 0;
+	pw->reclaimed = 0;
+
+	if (!vmevent_match_attr(attr, val))
+		return;
+
+	pw->samp->value = val;
+
+	atomic_set(&watch->pending, 1);
+	wake_up(&watch->waitq);
+}
+
+static void vmevent_pressure_tlet_fn(ulong data)
+{
+	struct vmevent_pwatcher *pw;
+	uint s;
+	uint r;
+
+	if (!vmevent_scanned)
+		return;
+
+	spin_lock(&vmevent_pressure_lock);
+	s = vmevent_scanned;
+	r = vmevent_reclaimed;
+	vmevent_scanned = 0;
+	vmevent_reclaimed = 0;
+	spin_unlock(&vmevent_pressure_lock);
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(pw, &vmevent_pwatchers, node) {
+		pw->scanned += s;
+		pw->reclaimed += r;
+		if (pw->scanned >= pw->window)
+			vmevent_match_pressure(pw);
+	}
+	rcu_read_unlock();
+}
+static DECLARE_TASKLET(vmevent_pressure_tlet, vmevent_pressure_tlet_fn, 0);
+
+void __vmevent_pressure(struct mem_cgroup *memcg,
+			ulong scanned,
+			ulong reclaimed)
+{
+	if (vmevent_minwin == UINT_MAX)
+		return;
+
+	spin_lock_bh(&vmevent_pressure_lock);
+
+	vmevent_scanned += scanned;
+	vmevent_reclaimed += reclaimed;
+
+	if (vmevent_scanned >= vmevent_minwin)
+		tasklet_schedule(&vmevent_pressure_tlet);
+
+	spin_unlock_bh(&vmevent_pressure_lock);
+}
+
 static unsigned int vmevent_poll(struct file *file, poll_table *wait)
 {
 	struct vmevent_watch *watch = file->private_data;
@@ -259,12 +371,40 @@ out:
 	return ret;
 }
 
+static void vmevent_release_pwatcher(struct vmevent_watch *watch)
+{
+	struct vmevent_pwatcher *pw;
+	struct vmevent_pwatcher *tmp;
+	struct vmevent_pwatcher *del = NULL;
+	int last = 1;
+
+	spin_lock(&vmevent_pwatchers_lock);
+
+	list_for_each_entry_safe(pw, tmp, &vmevent_pwatchers, node) {
+		if (pw->watch != watch) {
+			vmevent_minwin = min(pw->window, vmevent_minwin);
+			last = 0;
+			continue;
+		}
+		WARN_ON(del);
+		list_del_rcu(&pw->node);
+		del = pw;
+	}
+
+	if (last)
+		vmevent_minwin = UINT_MAX;
+
+	spin_unlock(&vmevent_pwatchers_lock);
+	synchronize_rcu();
+	kfree(del);
+}
+
 static int vmevent_release(struct inode *inode, struct file *file)
 {
 	struct vmevent_watch *watch = file->private_data;
 
 	cancel_delayed_work_sync(&watch->work);
-
+	vmevent_release_pwatcher(watch);
 	kfree(watch);
 
 	return 0;
@@ -289,6 +429,36 @@ static struct vmevent_watch *vmevent_watch_alloc(void)
 	return watch;
 }
 
+static int vmevent_setup_pwatcher(struct vmevent_watch *watch,
+				  struct vmevent_attr *attr,
+				  struct vmevent_attr *samp)
+{
+	struct vmevent_pwatcher *pw;
+
+	if (attr->type != VMEVENT_ATTR_PRESSURE)
+		return 0;
+
+	if (!attr->value2)
+		return -EINVAL;
+
+	pw = kzalloc(sizeof(*pw), GFP_KERNEL);
+	if (!pw)
+		return -ENOMEM;
+
+	pw->watch = watch;
+	pw->attr = attr;
+	pw->samp = samp;
+	pw->window = (attr->value2 + PAGE_SIZE - 1) / PAGE_SIZE;
+
+	vmevent_minwin = min(pw->window, vmevent_minwin);
+
+	spin_lock(&vmevent_pwatchers_lock);
+	list_add_rcu(&pw->node, &vmevent_pwatchers);
+	spin_unlock(&vmevent_pwatchers_lock);
+
+	return 0;
+}
+
 static int vmevent_setup_watch(struct vmevent_watch *watch)
 {
 	struct vmevent_config *config = &watch->config;
@@ -302,6 +472,7 @@ static int vmevent_setup_watch(struct vmevent_watch *watch)
 		struct vmevent_attr *attr = &config->attrs[i];
 		size_t size;
 		void *new;
+		int ret;
 
 		if (attr->type >= VMEVENT_ATTR_MAX)
 			continue;
@@ -322,6 +493,12 @@ static int vmevent_setup_watch(struct vmevent_watch *watch)
 
 		watch->config_attrs[nr] = attr;
 
+		ret = vmevent_setup_pwatcher(watch, attr, &attrs[nr]);
+		if (ret) {
+			kfree(attrs);
+			return ret;
+		}
+
 		nr++;
 	}
 
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 99b434b..f4dd1e0 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -20,6 +20,7 @@
 #include <linux/init.h>
 #include <linux/highmem.h>
 #include <linux/vmstat.h>
+#include <linux/vmevent.h>
 #include <linux/file.h>
 #include <linux/writeback.h>
 #include <linux/blkdev.h>
@@ -1334,6 +1335,9 @@ shrink_inactive_list(unsigned long nr_to_scan, struct lruvec *lruvec,
 		nr_scanned, nr_reclaimed,
 		sc->priority,
 		trace_shrink_flags(file));
+
+	vmevent_pressure(sc->target_mem_cgroup, nr_scanned, nr_reclaimed);
+
 	return nr_reclaimed;
 }
 
