diff --git a/src/lib-sieve/sieve-common.h b/src/lib-sieve/sieve-common.h
index ea1f420f7216a1b344c484ddf67f8874dd34d689..b2a218328212e2778d74a32cd765fec7e777e9d5 100644
--- a/src/lib-sieve/sieve-common.h
+++ b/src/lib-sieve/sieve-common.h
@@ -3,8 +3,7 @@
 
 #include "lib.h"
 
-#include "sieve-config.h"
-#include "sieve-types.h"
+#include "sieve.h"
 
 #include <sys/types.h>
 
diff --git a/src/lib-sieve/sieve-limits.h b/src/lib-sieve/sieve-limits.h
index 8e60b448ec912121a5bd734cfc86844cfb41724d..1a767b29525fb3d9619cd83b9e9ae564fad059aa 100644
--- a/src/lib-sieve/sieve-limits.h
+++ b/src/lib-sieve/sieve-limits.h
@@ -31,6 +31,7 @@
  */
 
 #define SIEVE_MAX_MATCH_VALUES                          32
+#define SIEVE_HIGH_CPU_TIME_MSECS                       1500
 #define SIEVE_DEFAULT_MAX_CPU_TIME_SECS                 30
 
 /*
diff --git a/src/lib-sieve/sieve-settings.c b/src/lib-sieve/sieve-settings.c
index b9b60f5d4b90dafe5b6268e6faba91e61b3d0eec..ead6029fbcebc4867e9527cf7ce5169f44a0e4f5 100644
--- a/src/lib-sieve/sieve-settings.c
+++ b/src/lib-sieve/sieve-settings.c
@@ -219,8 +219,8 @@ void sieve_settings_load(struct sieve_instance *svinst)
 	svinst->max_cpu_time_secs = SIEVE_DEFAULT_MAX_CPU_TIME_SECS;
 	if (sieve_setting_get_duration_value(svinst, "sieve_max_cpu_time",
 					     &period)) {
-		if (period > UINT_MAX)
-			svinst->max_cpu_time_secs = UINT_MAX;
+		if (period > (UINT_MAX / 1000))
+			svinst->max_cpu_time_secs = (UINT_MAX / 1000);
 		else
 			svinst->max_cpu_time_secs = (unsigned int)period;
 	}
diff --git a/src/lib-sieve/sieve-types.h b/src/lib-sieve/sieve-types.h
index df8928c9a4b1fecba3e787f88979a417853e30a6..e90f6da6eb7827f5027949d8b62ba4badc3c18af 100644
--- a/src/lib-sieve/sieve-types.h
+++ b/src/lib-sieve/sieve-types.h
@@ -256,6 +256,16 @@ struct sieve_script_env {
 #define SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) \
 	(senv->default_mailbox == NULL ? "INBOX" : senv->default_mailbox )
 
+/*
+ * Resource usage
+ */
+
+struct sieve_resource_usage {
+	/* The total amount of system + user CPU time consumed while executing
+	   the Sieve script. */
+	unsigned int cpu_time_msecs;
+};
+
 /*
  * Script execution status
  */
diff --git a/src/lib-sieve/sieve.c b/src/lib-sieve/sieve.c
index db1c246ab36e379704388ab7f3e165c5c2647105..b93ad560e4f78a61607095801b7bbd3b5f2a0dac 100644
--- a/src/lib-sieve/sieve.c
+++ b/src/lib-sieve/sieve.c
@@ -33,6 +33,7 @@
 
 #include "sieve.h"
 #include "sieve-common.h"
+#include "sieve-limits.h"
 #include "sieve-error-private.h"
 
 #include <sys/types.h>
@@ -1181,3 +1182,44 @@ const char *sieve_get_postmaster_address(const struct sieve_script_env *senv)
 	message_address_write(addr, postmaster);
 	return str_c(addr);
 }
+
+/*
+ * Resource usage
+ */
+
+void sieve_resource_usage_init(struct sieve_resource_usage *rusage_r)
+{
+	i_zero(rusage_r);
+}
+
+void sieve_resource_usage_add(struct sieve_resource_usage *dst,
+			      const struct sieve_resource_usage *src)
+{
+	if ((UINT_MAX - dst->cpu_time_msecs) < src->cpu_time_msecs)
+		dst->cpu_time_msecs = UINT_MAX;
+	else
+		dst->cpu_time_msecs += src->cpu_time_msecs;
+}
+
+bool sieve_resource_usage_is_high(struct sieve_instance *svinst ATTR_UNUSED,
+				  const struct sieve_resource_usage *rusage)
+{
+	return (rusage->cpu_time_msecs > SIEVE_HIGH_CPU_TIME_MSECS);
+}
+
+bool sieve_resource_usage_is_excessive(
+	struct sieve_instance *svinst,
+	const struct sieve_resource_usage *rusage)
+{
+	i_assert(svinst->max_cpu_time_secs <= (UINT_MAX / 1000));
+	return (rusage->cpu_time_msecs > (svinst->max_cpu_time_secs * 1000));
+}
+
+const char *
+sieve_resource_usage_get_summary(const struct sieve_resource_usage *rusage)
+{
+	if (rusage->cpu_time_msecs == 0)
+		return "no usage recorded";
+
+	return t_strdup_printf("cpu time = %u ms", rusage->cpu_time_msecs);
+}
diff --git a/src/lib-sieve/sieve.h b/src/lib-sieve/sieve.h
index 3cbf2771e4d723953be0cd316bc9fb01b0b47375..16bbffc54d5c3c99dd38baa0ec8e88fbe4d3bfbb 100644
--- a/src/lib-sieve/sieve.h
+++ b/src/lib-sieve/sieve.h
@@ -212,4 +212,31 @@ void sieve_trace_log_free(struct sieve_trace_log **_trace_log);
 int sieve_trace_config_get(struct sieve_instance *svinst,
 			   struct sieve_trace_config *tr_config);
 
+/*
+ * Resource usage
+ */
+
+/* Initialize the resource usage struct, clearing all usage statistics. */
+void sieve_resource_usage_init(struct sieve_resource_usage *rusage_r);
+
+/* Calculate the sum of the provided resource usage statistics, writing the
+   result to the first. */
+void sieve_resource_usage_add(struct sieve_resource_usage *dst,
+			      const struct sieve_resource_usage *src);
+
+/* Returns TRUE if the resource usage is sufficiently high to warrant recording
+   for checking cumulative resource limits (across several different script
+   executions). */
+bool sieve_resource_usage_is_high(struct sieve_instance *svinst,
+				  const struct sieve_resource_usage *rusage);
+/* Returns TRUE when the provided resource usage statistics exceed a configured
+   policy limit. */
+bool sieve_resource_usage_is_excessive(
+	struct sieve_instance *svinst,
+	const struct sieve_resource_usage *rusage);
+/* Returns a string containing a description of the resource usage (to be used
+   log messages). */
+const char *
+sieve_resource_usage_get_summary(const struct sieve_resource_usage *rusage);
+
 #endif