diff --git a/SOURCES/measure.patch b/SOURCES/measure.patch
new file mode 100644
index 0000000..b0c580f
--- /dev/null
+++ b/SOURCES/measure.patch
@@ -0,0 +1,279 @@
+From 517a37ae7beeb77e2b4870be11611f82c1200b3c Mon Sep 17 00:00:00 2001
+From: Matthew Almond <malmond@fb.com>
+Date: Thu, 11 Jun 2020 13:01:04 -0700
+Subject: [PATCH 2/2] Measure plugin
+
+---
+ macros.in           |   1 +
+ plugins/Makefile.am |   4 +
+ plugins/measure.c   | 231 ++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 236 insertions(+)
+ create mode 100644 plugins/measure.c
+
+diff --git a/macros.in b/macros.in
+index 3cc8a3555..c8a087959 100644
+--- a/macros.in
++++ b/macros.in
+@@ -1173,6 +1173,7 @@ package or when debugging this package.\
+ # Transaction plugin macros
+ %__plugindir		%{_libdir}/rpm-plugins
+ %__transaction_reflink		%{__plugindir}/reflink.so
++%__transaction_measure		%{__plugindir}/measure.so
+ %__transaction_systemd_inhibit	%{__plugindir}/systemd_inhibit.so
+ %__transaction_selinux		%{__plugindir}/selinux.so
+ %__transaction_syslog		%{__plugindir}/syslog.so
+diff --git a/plugins/Makefile.am b/plugins/Makefile.am
+index 06393ce8d..daed6423c 100644
+--- a/plugins/Makefile.am
++++ b/plugins/Makefile.am
+@@ -29,6 +29,10 @@ systemd_inhibit_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/
+ plugins_LTLIBRARIES += systemd_inhibit.la
+ endif
+ 
++measure_la_SOURCES = measure.c
++measure_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
++plugins_LTLIBRARIES += measure.la
++
+ prioreset_la_SOURCES = prioreset.c
+ prioreset_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
+ plugins_LTLIBRARIES += prioreset.la
+diff --git a/plugins/measure.c b/plugins/measure.c
+new file mode 100644
+index 000000000..2cdfc885e
+--- /dev/null
++++ b/plugins/measure.c
+@@ -0,0 +1,231 @@
++#include "system.h"
++#include "time.h"
++
++#include <rpm/rpmlog.h>
++#include <rpm/rpmmacro.h>
++#include <rpm/rpmts.h>
++#include "lib/rpmlib.h"
++
++#include "lib/rpmplugin.h"
++
++struct measurestat {
++    /* We're counting psm not packages because packages often run psm_pre/post
++       more than once and we want to accumulate the time
++    */
++    unsigned int psm_count;
++    unsigned int scriptlet_count;
++    struct timespec plugin_start;
++    struct timespec psm_start;
++    struct timespec scriptlet_start;
++};
++
++static rpmRC push(const char *format, const char *value, const char *formatted)
++{
++    char *key = NULL;
++    rpmRC rc = RPMRC_FAIL;
++    if (formatted == NULL) {
++        /* yes we're okay with discarding const here */
++        key = (char *)format;
++    } else {
++        if (rasprintf(&key, format, formatted) == -1) {
++            rpmlog(
++                RPMLOG_ERR,
++                _("measure: Failed to allocate formatted key %s, %s\n"),
++                format,
++                formatted
++            );
++            goto exit;
++        }
++    }
++    if (rpmPushMacro(NULL, key, NULL, value, RMIL_GLOBAL)) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to set %s\n"), key);
++        goto exit;
++    }
++    rc = RPMRC_OK;
++exit:
++    if (formatted != NULL) {
++        free(key);
++    }
++    return rc;
++}
++
++static rpmRC diff_ms(char **ms, struct timespec *start, struct timespec *end)
++{
++    if (rasprintf(ms, "%ld", (
++        (end->tv_sec - start->tv_sec) * 1000 +
++        (end->tv_nsec - start->tv_nsec) / 1000000
++    )) == -1) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted ms\n"));
++        return RPMRC_FAIL;
++    }
++    return RPMRC_OK;
++}
++
++static rpmRC measure_init(rpmPlugin plugin, rpmts ts)
++{
++    rpmRC rc = RPMRC_FAIL;
++    struct measurestat *state = rcalloc(1, sizeof(*state));
++    if (clock_gettime(CLOCK_MONOTONIC, &state->plugin_start)) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to get plugin_start time\n"));
++        goto exit;
++    }
++    state->psm_count = 0;
++    state->scriptlet_count = 0;
++    rpmPluginSetData(plugin, state);
++    rc = RPMRC_OK;
++exit:
++    return rc;
++}
++
++static void measure_cleanup(rpmPlugin plugin)
++{
++    struct measurestat *state = rpmPluginGetData(plugin);
++    free(state);
++}
++
++static rpmRC measure_tsm_post(rpmPlugin plugin, rpmts ts, int res)
++{
++    struct measurestat *state = rpmPluginGetData(plugin);
++    char *psm_count = NULL, *scriptlet_count = NULL;
++    rpmRC rc = RPMRC_FAIL;
++
++    if (rasprintf(&psm_count, "%d", state->psm_count) == -1) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted psm_count\n"));
++        goto exit;
++    }
++    if (rasprintf(&scriptlet_count, "%d", state->scriptlet_count) == -1) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted scriptlet_count\n"));
++        goto exit;
++    }
++    if (push("_measure_plugin_psm_count", psm_count, NULL) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("_measure_plugin_scriptlet_count", scriptlet_count, NULL) != RPMRC_OK) {
++        goto exit;
++    }
++    rc = RPMRC_OK;
++exit:
++    free(psm_count);
++    free(scriptlet_count);
++    return rc;
++}
++
++static rpmRC measure_psm_pre(rpmPlugin plugin, rpmte te)
++{
++    struct measurestat *state = rpmPluginGetData(plugin);
++    rpmRC rc = RPMRC_FAIL;
++
++    if (clock_gettime(CLOCK_MONOTONIC, &state->psm_start)) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to get psm_start time\n"));
++        goto exit;
++    }
++    rc = RPMRC_OK;
++exit:
++    return rc;
++}
++
++static rpmRC measure_psm_post(rpmPlugin plugin, rpmte te, int res)
++{
++    struct measurestat *state = rpmPluginGetData(plugin);
++    struct timespec end;
++    char *offset = NULL, *duration = NULL, *prefix = NULL;
++    Header h = rpmteHeader(te);
++    rpmRC rc = RPMRC_FAIL;
++
++    if (clock_gettime(CLOCK_MONOTONIC, &end)) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to get psm end time\n"));
++        goto exit;
++    }
++    if (rasprintf(&prefix, "_measure_plugin_package_%u", state->psm_count) == -1) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to allocate prefix\n"));
++        goto exit;
++    }
++    if (diff_ms(&offset, &state->plugin_start, &state->psm_start) != RPMRC_OK) {
++        goto exit;
++    }
++    if (diff_ms(&duration, &state->psm_start, &end) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("%s_nevra", rpmteNEVRA(te), prefix) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("%s_compressor", headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR), prefix) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("%s_offset", offset, prefix) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("%s_ms", duration, prefix) != RPMRC_OK) {
++        goto exit;
++    }
++    state->psm_count += 1;
++    rc = RPMRC_OK;
++exit:
++    headerFree(h);
++    free(prefix);
++    free(duration);
++    free(offset);
++    return rc;
++}
++
++static rpmRC measure_scriptlet_pre(rpmPlugin plugin,
++					const char *s_name, int type)
++{
++    struct measurestat *state = rpmPluginGetData(plugin);
++    if (clock_gettime(CLOCK_MONOTONIC, &state->scriptlet_start)) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to get scriptlet_start time\n"));
++        return RPMRC_FAIL;
++    }
++    return RPMRC_OK;
++}
++
++static rpmRC measure_scriptlet_post(rpmPlugin plugin,
++					const char *s_name, int type, int res)
++{
++    struct measurestat *state = rpmPluginGetData(plugin);
++    struct timespec end;
++    char *offset = NULL, *duration = NULL, *prefix = NULL;
++    rpmRC rc = RPMRC_FAIL;
++
++    if (clock_gettime(CLOCK_MONOTONIC, &end)) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to get end time\n"));
++        goto exit;
++    }
++
++    if (rasprintf(&prefix, "_measure_plugin_scriptlet_%d", state->scriptlet_count) == -1) {
++        rpmlog(RPMLOG_ERR, _("measure: Failed to allocate formatted prefix\n"));
++        goto exit;
++    }
++    if (diff_ms(&offset, &state->plugin_start, &state->scriptlet_start) != RPMRC_OK) {
++        goto exit;
++    }
++    if (diff_ms(&duration, &state->scriptlet_start, &end) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("%s_name", s_name, prefix) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("%s_offset", offset, prefix) != RPMRC_OK) {
++        goto exit;
++    }
++    if (push("%s_ms", duration, prefix) != RPMRC_OK) {
++        goto exit;
++    }
++    state->scriptlet_count += 1;
++    rc = RPMRC_OK;
++exit:
++    free(prefix);
++    free(duration);
++    free(offset);
++    return rc;
++}
++
++struct rpmPluginHooks_s measure_hooks = {
++    .init = measure_init,
++    .cleanup = measure_cleanup,
++    .tsm_post = measure_tsm_post,
++    .psm_pre = measure_psm_pre,
++    .psm_post = measure_psm_post,
++    .scriptlet_pre = measure_scriptlet_pre,
++    .scriptlet_post = measure_scriptlet_post,
++};
+-- 
+2.24.1
+
diff --git a/SPECS/rpm.spec b/SPECS/rpm.spec
index bf701e7..5a527c6 100644
--- a/SPECS/rpm.spec
+++ b/SPECS/rpm.spec
@@ -32,7 +32,7 @@
 
 %global rpmver 4.14.3
 #global snapver rc2
-%global rel 15.3
+%global rel 15.4
 
 %global srcver %{version}%{?snapver:-%{snapver}}
 %global srcdir %{?snapver:testing}%{!?snapver:%{name}-%(echo %{version} | cut -d'.' -f1-2).x}
@@ -183,6 +183,7 @@ Patch9990: https://github.com/rpm-software-management/rpm/pull/1381.patch
 Provides: rpm(pr1381)
 Patch9991: https://github.com/rpm-software-management/rpm/pull/1470.patch
 Provides: rpm(pr1470)
+Patch9999: measure.patch
 
 # Partially GPL/LGPL dual-licensed and some bits with BSD
 # SourceLicense: (GPLv2+ and LGPLv2+ with exceptions) and BSD 
@@ -479,6 +480,14 @@ Requires: rpm-libs%{_isa} = %{version}-%{release}
 %description plugin-reflink
 %{summary}.
 
+%package plugin-measure
+Summary: Rpm plugin for measure
+Group: System Environment/Base
+Requires: rpm-libs%{_isa} = %{version}-%{release}
+
+%description plugin-measure
+Adds measure support
+
 %endif # with plugins
 
 %prep
@@ -698,6 +707,9 @@ make check || cat tests/rpmtests.log
 %files plugin-reflink
 %{_bindir}/rpm2extents
 %{_libdir}/rpm-plugins/reflink.so
+
+%files plugin-measure
+%{_libdir}/rpm-plugins/measure.so
 %endif # with plugins
 
 %files build-libs
@@ -760,6 +772,9 @@ make check || cat tests/rpmtests.log
 %doc doc/librpm/html/*
 
 %changelog
+* Tue Aug 03 2021 Matthew Almond <malmond@fb.com> - 4.14.3-15.4
+- Add measure plugin
+
 * Tue Aug 03 2021 Matthew Almond <malmond@fb.com> - 4.14.3-15.3
 - Move rpm2extents to plugin package