diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index f33781e..b6eb55d 100644
--- a/core/modules/migrate/src/MigrateExecutable.php
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -27,20 +27,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
   protected $migration;
 
   /**
-   * The number of successfully imported rows since feedback was given.
-   *
-   * @var int
-   */
-  protected $successesSinceFeedback;
-
-  /**
-   * The number of rows that were successfully processed.
-   *
-   * @var int
-   */
-  protected $totalSuccesses;
-
-  /**
    * Status of one row.
    *
    * The value is a MigrateIdMapInterface::STATUS_* constant, for example:
@@ -51,15 +37,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
   protected $sourceRowStatus;
 
   /**
-   * The number of rows processed.
-   *
-   * The total attempted, whether or not they were successful.
-   *
-   * @var int
-   */
-  protected $totalProcessed;
-
-  /**
    * The queued messages not yet saved.
    *
    * Each element in the array is an array with two keys:
@@ -81,6 +58,13 @@ class MigrateExecutable implements MigrateExecutableInterface {
   protected $options;
 
   /**
+   * An array keyed by counter name for tracking statistics.
+   *
+   * @var array
+   */
+  protected $counters = array();
+
+  /**
    * The PHP max_execution_time.
    *
    * @var int
@@ -116,13 +100,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
   protected $sourceIdValues;
 
   /**
-   * The number of rows processed since feedback was given.
-   *
-   * @var int
-   */
-  protected $processedSinceFeedback = 0;
-
-  /**
    * The PHP memory_limit expressed in bytes.
    *
    * @var int
@@ -187,6 +164,7 @@ public function __construct(MigrationInterface $migration, MigrateMessageInterfa
     $this->migration = $migration;
     $this->message = $message;
     $this->migration->getIdMap()->setMessage($message);
+
     // Record the memory limit in bytes
     $limit = trim(ini_get('memory_limit'));
     if ($limit == '-1') {
@@ -280,6 +258,7 @@ public function import() {
       catch (MigrateSkipRowException $e) {
         $id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED, $this->rollbackAction);
         $save = FALSE;
+        $this->incrementCounter('skips');
       }
 
       if ($save) {
@@ -290,11 +269,11 @@ public function import() {
             if ($destination_id_values !== TRUE) {
               $id_map->saveIdMapping($row, $destination_id_values, $this->sourceRowStatus, $this->rollbackAction);
             }
-            $this->successesSinceFeedback++;
-            $this->totalSuccesses++;
+            $this->incrementCounter('successes');
           }
           else {
             $id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
+            $this->incrementCounter('errors');
             if (!$id_map->messageCount()) {
               $message = $this->t('New object was not saved, no error provided');
               $this->saveMessage($message);
@@ -306,14 +285,15 @@ public function import() {
           $this->migration->getIdMap()->saveIdMapping($row, array(), $e->getStatus(), $this->rollbackAction);
           $this->saveMessage($e->getMessage(), $e->getLevel());
           $this->message->display($e->getMessage(), 'error');
+          $this->incrementCounter('errors');
         }
         catch (\Exception $e) {
           $this->migration->getIdMap()->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
           $this->handleException($e);
+          $this->incrementCounter('errors');
         }
       }
-      $this->totalProcessed++;
-      $this->processedSinceFeedback++;
+      $this->incrementCounter('processed');
       if ($high_water_property = $this->migration->get('highWaterProperty')) {
         $this->migration->saveHighWater($row->getSourceProperty($high_water_property['name']));
       }
@@ -605,6 +585,39 @@ protected function getTimeElapsed() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function resetCounter($name) {
+    $this->counters[$name] = 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function incrementCounter($name) {
+    if (!isset($this->counters[$name])) {
+      $this->counters[$name] = 0;
+    }
+    $this->counters[$name]++;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCount($name) {
+    return isset($this->counters[$name]) ? $this->counters[$name] : 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCounterList() {
+    $counter_names = array_keys($this->counters);
+    sort($counter_names, SORT_NATURAL | SORT_FLAG_CASE);
+    return $counter_names;
+  }
+
+  /**
    * Takes an Exception object and both saves and displays it.
    *
    * Pulls in additional information on the location triggering the exception.
diff --git a/core/modules/migrate/src/MigrateExecutableInterface.php b/core/modules/migrate/src/MigrateExecutableInterface.php
index 4a362fe..14672ce 100644
--- a/core/modules/migrate/src/MigrateExecutableInterface.php
+++ b/core/modules/migrate/src/MigrateExecutableInterface.php
@@ -44,6 +44,7 @@ public function processRow(Row $row, array $process = NULL, $value = NULL);
    */
   public function getTimeLimit();
 
+
   /**
    * Passes messages through to the map class.
    *
@@ -68,4 +69,36 @@ public function queueMessage($message, $level = MigrationInterface::MESSAGE_ERRO
    * Saves any messages we've queued up to the message table.
    */
   public function saveQueuedMessages();
+
+
+  /**
+   * Reset the named counter to 0.
+   *
+   * @param $name
+   */
+  public function resetCounter($name);
+
+  /**
+   * Add one to the named counter, or set it to 1 if not previously set.
+   *
+   * @param $name
+   */
+  public function incrementCounter($name);
+
+  /**
+   * Retrieve the named counter, default to 0 if not previously set.
+   *
+   * @param $name
+   *
+   * @return int
+   */
+  public function getCount($name);
+
+  /**
+   * Retrieve a sorted list of all counter names.
+   *
+   * @return array
+   */
+  public function getCounterList();
+
 }
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php b/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php
index 084fd88..960cd41 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php
@@ -108,11 +108,4 @@ public function getUpdated() {
     // TODO: Implement getUpdated() method.
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function resetStats() {
-    // TODO: Implement resetStats() method.
-  }
-
 }
diff --git a/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php b/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php
index 9a17806..14d3fee 100644
--- a/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php
+++ b/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php
@@ -60,20 +60,6 @@
   protected $currentSourceIds;
 
   /**
-   * Number of rows intentionally ignored (prepareRow() returned FALSE)
-   *
-   * @var int
-   */
-  protected $numIgnored = 0;
-
-  /**
-   * Number of rows we've at least looked at.
-   *
-   * @var int
-   */
-  protected $numProcessed = 0;
-
-  /**
    * The high water mark at the beginning of the import operation.
    *
    * If the source has a property for tracking changes (like Drupal ha
@@ -214,7 +200,6 @@ public function prepareRow(Row $row) {
       $id_map->delete($this->currentSourceIds, TRUE);
       $this->migrateExecutable->saveQueuedMessages();
       $id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED, $this->migrateExecutable->rollbackAction);
-      $this->numIgnored++;
       $this->currentRow = NULL;
       $this->currentSourceIds = NULL;
       $result = FALSE;
@@ -226,7 +211,7 @@ public function prepareRow(Row $row) {
       // after hashes).
       $row->rehash();
     }
-    $this->numProcessed++;
+
     return $result;
   }
 
@@ -280,8 +265,6 @@ public function valid() {
    */
   public function rewind() {
     $this->idMap = $this->migration->getIdMap();
-    $this->numProcessed = 0;
-    $this->numIgnored = 0;
     $this->getIterator()->rewind();
     $this->next();
   }
@@ -378,27 +361,6 @@ public function getCurrentIds() {
   }
 
   /**
-   * Getter for numIgnored data member.
-   */
-  public function getIgnored() {
-    return $this->numIgnored;
-  }
-
-  /**
-   * Getter for numProcessed data member.
-   */
-  public function getProcessed() {
-    return $this->numProcessed;
-  }
-
-  /**
-   * Reset numIgnored back to 0.
-   */
-  public function resetStats() {
-    $this->numIgnored = 0;
-  }
-
-  /**
    * Get the source count.
    *
    * Return a count of available source records, from the cache if appropriate.
diff --git a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
index f862ae8..a482a76 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
@@ -121,10 +121,13 @@ public function testImportWithValidRow() {
 
     $this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
 
-    $this->assertSame(1, $this->executable->getSuccessesSinceFeedback());
-    $this->assertSame(1, $this->executable->getTotalSuccesses());
-    $this->assertSame(1, $this->executable->getTotalProcessed());
-    $this->assertSame(1, $this->executable->getProcessedSinceFeedback());
+    $this->assertSame(1, $this->executable->getCount('successes'));
+    $this->assertSame(1, $this->executable->getCount('processed'));
+
+    $this->executable->resetCounter('successes');
+    $this->assertSame(0, $this->executable->getCount('successes'));
+
+    $this->assertArrayEquals(array('processed', 'successes'), $this->executable->getCounterList());
   }
 
   /**
@@ -171,10 +174,8 @@ public function testImportWithValidRowWithoutDestinationId() {
 
     $this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
 
-    $this->assertSame(1, $this->executable->getSuccessesSinceFeedback());
-    $this->assertSame(1, $this->executable->getTotalSuccesses());
-    $this->assertSame(1, $this->executable->getTotalProcessed());
-    $this->assertSame(1, $this->executable->getProcessedSinceFeedback());
+    $this->assertSame(1, $this->executable->getCount('successes'));
+    $this->assertSame(1, $this->executable->getCount('processed'));
   }
 
   /**
diff --git a/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php b/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
index f26bc56..11f22c1 100644
--- a/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
+++ b/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
@@ -77,46 +77,6 @@ public function getMaxExecTime() {
   }
 
   /**
-   * Allows access to protected successesSinceFeedback property.
-   *
-   * @return int
-   *   The value of the protected property.
-   */
-  public function getSuccessesSinceFeedback() {
-    return $this->successesSinceFeedback;
-  }
-
-  /**
-   * Allows access to protected totalSuccesses property.
-   *
-   * @return int
-   *   The value of the protected property.
-   */
-  public function getTotalSuccesses() {
-    return $this->totalSuccesses;
-  }
-
-  /**
-   * Allows access to protected totalProcessed property.
-   *
-   * @return int
-   *   The value of the protected property.
-   */
-  public function getTotalProcessed() {
-    return $this->totalProcessed;
-  }
-
-  /**
-   * Allows access to protected processedSinceFeedback property.
-   *
-   * @return int
-   *   The value of the protected property.
-   */
-  public function getProcessedSinceFeedback() {
-    return $this->processedSinceFeedback;
-  }
-
-  /**
    * Allows access to protected maxExecTimeExceeded method.
    *
    * @return bool
