diff --git a/examples/wave/wave-eager.py b/examples/wave/wave-eager.py
index e78214313bfb8ea0ab9e547bbc37d6ebc0aaaa5f..7450f435798c249f97dd11e03cd59dbd1e95b542 100644
--- a/examples/wave/wave-eager.py
+++ b/examples/wave/wave-eager.py
@@ -163,7 +163,8 @@ def main():
         fields = rk4_step(fields, t, dt, rhs)
 
         if istep % 10 == 0:
-            print(istep, t, discr.norm(fields[0]))
+            print(f"step: {istep} t: {t} L2: {discr.norm(fields[0])} "
+                    f"sol max: {discr.nodal_max('vol', fields[0])}")
             vis.write_vtk_file("fld-wave-eager-%04d.vtu" % istep,
                     [
                         ("u", fields[0]),
diff --git a/grudge/eager.py b/grudge/eager.py
index 07cb0f046c5cfb9187973b7b6fa44c4cb435cdab..71a01873001d56883d49754bd4264f18c3c20935 100644
--- a/grudge/eager.py
+++ b/grudge/eager.py
@@ -166,6 +166,19 @@ class EagerDGDiscretization(DGDiscretizationWithBoundaries):
     def norm(self, vec, p=2):
         return self._norm(p)(arg=vec)
 
+    @memoize_method
+    def _nodal_reduction(self, operator, dd):
+        return bind(self, operator(dd)(sym.var("arg")), local_only=True)
+
+    def nodal_sum(self, dd, vec):
+        return self._nodal_reduction(sym.NodalSum, dd)(arg=vec)
+
+    def nodal_min(self, dd, vec):
+        return self._nodal_reduction(sym.NodalMin, dd)(arg=vec)
+
+    def nodal_max(self, dd, vec):
+        return self._nodal_reduction(sym.NodalMax, dd)(arg=vec)
+
     @memoize_method
     def connected_ranks(self):
         from meshmode.distributed import get_connected_partitions