diff --git a/src/main/java/org/opennars/lab/metric/MetricListener.java b/src/main/java/org/opennars/lab/metric/MetricListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..c949aa198754abb59cbe6af145185b7a52dd3056
--- /dev/null
+++ b/src/main/java/org/opennars/lab/metric/MetricListener.java
@@ -0,0 +1,21 @@
+package org.opennars.lab.metric;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Listener after GOF for metric events
+ */
+public class MetricListener {
+    public void register(MetricObserver obs) {
+        observers.add(obs);
+    }
+
+    public void notifyObservers(String name, int value) {
+        for(MetricObserver iObserver:observers) {
+            iObserver.notifyInt(name, value);
+        }
+    }
+
+    public List<MetricObserver> observers = new ArrayList<>();
+}
diff --git a/src/main/java/org/opennars/lab/metric/MetricObserver.java b/src/main/java/org/opennars/lab/metric/MetricObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e2847377bc88235449d61db87863418fe2dfa52
--- /dev/null
+++ b/src/main/java/org/opennars/lab/metric/MetricObserver.java
@@ -0,0 +1,12 @@
+package org.opennars.lab.metric;
+
+/**
+ * GOF observer for metrics
+ */
+public abstract class MetricObserver {
+    public MetricObserver(MetricListener listener) {
+        listener.register(this);
+    }
+
+    abstract public void notifyInt(String name, int value);
+}
diff --git a/src/main/java/org/opennars/lab/metric/MetricReporter.java b/src/main/java/org/opennars/lab/metric/MetricReporter.java
index 7e5ef8a5b94ebb5f32a2e3b954f267af6f9c1de3..5d9feec5bf36d52d55af120dd25bf2db296cca00 100644
--- a/src/main/java/org/opennars/lab/metric/MetricReporter.java
+++ b/src/main/java/org/opennars/lab/metric/MetricReporter.java
@@ -4,50 +4,38 @@ import org.opennars.main.Nar;
 
 import java.io.IOException;
 import java.net.*;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
+import java.util.*;
 
+/**
+ * reports metric to a receiver over UDP (for graphite)
+ */
 public class MetricReporter {
-    public List<MetricSensor> sensors = new ArrayList<>();
-
     public String narsVersion = Nar.VERSION;
     public long runId = new Random().nextInt();
 
+    public List<MetricObserver> observers = new ArrayList<>();
+
+
+    private Map<String, Integer> integerMap = new HashMap<>();
+
     public void connect(String targetHost, int targetPort) throws UnknownHostException {
         receiverTarget = InetAddress.getByName(targetHost);
         receiverTargetPort = targetPort;
     }
 
-    public void sendFromAllSensors() {
-        for(final MetricSensor iSensor : sensors) {
-            String valueAsString = iSensor.getValueAsString(false);
-
-            if (valueAsString != null) {
-                send(valueAsString, iSensor.getName());
-            }
-        }
+    public void notifyInt(String name, int value) {
+        integerMap.put(name, value);
 
-        for(final MetricSensor iSensor : sensors) {
-            iSensor.resetAfterSending();
-        }
+        pushReport();
     }
 
-    // forces sensors to send their data - preferably called every second
-    public void sendFromAllSensorsPerSecondTick() {
-        for(final MetricSensor iSensor : sensors) {
-            String valueAsString = iSensor.getValueAsString(true);
-
-            if (valueAsString != null) {
-                send(valueAsString, iSensor.getName());
-            }
-        }
-
-        for(final MetricSensor iSensor : sensors) {
-            iSensor.resetAfterSending();
+    private void pushReport() {
+        for (Map.Entry<String, Integer> iEntry: integerMap.entrySet()) {
+            send(Integer.toString(iEntry.getValue()), iEntry.getKey());
         }
     }
 
+
     private void send(final String dataAsString, final String metricPathName) {
         final String timestampAsString = "-1"; // -1 leads to automatic timestamping on arrival of the message
 
@@ -72,4 +60,7 @@ public class MetricReporter {
     private InetAddress receiverTarget;
     private int receiverTargetPort;
 
+    public void register(MetricObserver metricObserver) {
+        observers.add(metricObserver);
+    }
 }
diff --git a/src/main/java/org/opennars/lab/metric/MetricReporterObserver.java b/src/main/java/org/opennars/lab/metric/MetricReporterObserver.java
new file mode 100644
index 0000000000000000000000000000000000000000..e29246a683660962302a293e4c95d55f4f9fc431
--- /dev/null
+++ b/src/main/java/org/opennars/lab/metric/MetricReporterObserver.java
@@ -0,0 +1,18 @@
+package org.opennars.lab.metric;
+
+/**
+ * observer which reports the metric as an UDP message
+ */
+public class MetricReporterObserver extends MetricObserver {
+    private final MetricReporter reporter;
+
+    public MetricReporterObserver(MetricListener listener, MetricReporter reporter) {
+        super(listener);
+        this.reporter = reporter;
+    }
+
+    @Override
+    public void notifyInt(String name, int value) {
+        reporter.notifyInt(name, value);
+    }
+}
diff --git a/src/main/java/org/opennars/lab/microworld/Pong.java b/src/main/java/org/opennars/lab/microworld/Pong.java
index a58a064c055fbbfe0dd6b102f20cdf89ace5379e..6352a9408e52e5f30b48e84ae6d3be6bfae808ff 100755
--- a/src/main/java/org/opennars/lab/microworld/Pong.java
+++ b/src/main/java/org/opennars/lab/microworld/Pong.java
@@ -14,8 +14,7 @@
  */
 package org.opennars.lab.microworld;
 
-import org.opennars.lab.metric.MetricReporter;
-import org.opennars.lab.metric.MetricSensor;
+import org.opennars.lab.metric.*;
 import org.opennars.storage.Memory;
 import org.opennars.main.Nar;
 //import org.opennars.nal.nal8.Operation;
@@ -55,106 +54,18 @@ public class Pong extends Frame {
     public int t = 0;
 
     public MetricReporter metricReporter;
+    public MetricListener metricListener;
+    public MetricObserver metricObserver;
 
     public long lastSecondTickTime = 0;
 
     public Pong() throws UnknownHostException {
+        metricListener = new MetricListener();
+
         metricReporter = new MetricReporter();
         metricReporter.connect("127.0.0.1", 8125);
 
-        metricReporter.sensors.add(new MetricSensor() {
-            private int oldBallHits = 0;
-
-            @Override
-            public String getName() {
-                return "ballHitsDelta";
-            }
-
-            @Override
-            public String getValueAsString(boolean force) {
-                if (oldBallHits == ballHits) {
-                    return null; // don't send anything
-                }
-
-                return "" + (ballHits-oldBallHits);
-            }
-
-            @Override
-            public void resetAfterSending() {
-                oldBallHits = ballHits;
-            }
-        });
-
-        metricReporter.sensors.add(new MetricSensor() {
-            private int oldBallMisses = 0;
-
-            @Override
-            public String getName() {
-                return "ballMissesDelta";
-            }
-
-            @Override
-            public String getValueAsString(boolean force) {
-                if (oldBallMisses == ballMisses) {
-                    return null; // don't send anything
-                }
-
-                return "" + (ballMisses-oldBallMisses);
-            }
-
-            @Override
-            public void resetAfterSending() {
-                oldBallMisses = ballMisses;
-            }
-        });
-
-
-        metricReporter.sensors.add(new MetricSensor() {
-            private int oldBallHits = 0;
-
-            @Override
-            public String getName() {
-                return "ballHits";
-            }
-
-            @Override
-            public String getValueAsString(boolean force) {
-                if (!force && oldBallHits == ballHits) {
-                    return null; // don't send anything
-                }
-
-                return "" + (ballHits);
-            }
-
-            @Override
-            public void resetAfterSending() {
-                oldBallHits = ballHits;
-            }
-        });
-
-        metricReporter.sensors.add(new MetricSensor() {
-            private int oldBallMisses = 0;
-
-            @Override
-            public String getName() {
-                return "ballMisses";
-            }
-
-            @Override
-            public String getValueAsString(boolean force) {
-                if (!force && oldBallMisses == ballMisses) {
-                    return null; // don't send anything
-                }
-
-                return "" + (ballMisses);
-            }
-
-            @Override
-            public void resetAfterSending() {
-                oldBallMisses = ballMisses;
-            }
-        });
-
+        metricObserver = new MetricReporterObserver(metricListener, metricReporter);
 
         String[] args = {"Pong"};
         MyPapplet mp = new MyPapplet ();
@@ -353,16 +264,6 @@ public class Pong extends Frame {
 
                 t++;
                 nar.cycles(10);
-                metricReporter.sendFromAllSensors();
-
-                {
-                    if( System.currentTimeMillis() - lastSecondTickTime >= 1000 ) {
-                        lastSecondTickTime = System.currentTimeMillis();
-
-                        metricReporter.sendFromAllSensorsPerSecondTick();
-                    }
-                }
-
 
                 if(lastAction==0 && random(1.0f) < Alpha) { //if Nar hasn't decided chose a executable random action
                     lastAction = (int) random((float) nActions);
@@ -584,9 +485,11 @@ public class Pong extends Frame {
 
                     if(diffAbs < middle_distance) {
                         ballHits++;
+                        metricObserver.notifyInt("hit", 1);
                     }
                     else {
                         ballMisses++;
+                        metricObserver.notifyInt("miss", 1);
                     }
                 } 
                 oi.x += oi.VX;