diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index f33781e..7cdaf9d 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 object for tracking statistics.
+   *
+   * @var \Drupal\migrate\MigrationCounters
+   */
+  protected $counters;
+
+  /**
    * 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
@@ -183,10 +160,14 @@ class MigrateExecutable implements MigrateExecutableInterface {
    *
    * @throws \Drupal\migrate\MigrateException
    */
-  public function __construct(MigrationInterface $migration, MigrateMessageInterface $message) {
+  public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, MigrationCountersInterface $counters = NULL) {
     $this->migration = $migration;
     $this->message = $message;
     $this->migration->getIdMap()->setMessage($message);
+    if (is_null($counters)) {
+      $counters = new MigrationCounters();
+    }
+    $this->counters = $counters;
     // Record the memory limit in bytes
     $limit = trim(ini_get('memory_limit'));
     if ($limit == '-1') {
@@ -280,6 +261,7 @@ public function import() {
       catch (MigrateSkipRowException $e) {
         $id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED, $this->rollbackAction);
         $save = FALSE;
+        $this->incrementSkips();
       }
 
       if ($save) {
@@ -290,11 +272,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->incrementSuccesses();
           }
           else {
             $id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
+            $this->incrementErrors();
             if (!$id_map->messageCount()) {
               $message = $this->t('New object was not saved, no error provided');
               $this->saveMessage($message);
@@ -306,14 +288,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->incrementErrors();
         }
         catch (\Exception $e) {
           $this->migration->getIdMap()->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
           $this->handleException($e);
+          $this->incrementErrors();
         }
       }
-      $this->totalProcessed++;
-      $this->processedSinceFeedback++;
+      $this->incrementProcessed();
       if ($high_water_property = $this->migration->get('highWaterProperty')) {
         $this->migration->saveHighWater($row->getSourceProperty($high_water_property['name']));
       }
@@ -605,6 +588,41 @@ protected function getTimeElapsed() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function getCounters() {
+    return $this->counters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function incrementSuccesses() {
+    $this->getCounters()->increment('successes');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function incrementProcessed() {
+    $this->getCounters()->increment('processed');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function incrementErrors() {
+    $this->getCounters()->increment('errors');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function incrementSkips() {
+    $this->getCounters()->increment('skips');
+  }
+
+  /**
    * 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..7aba249 100644
--- a/core/modules/migrate/src/MigrateExecutableInterface.php
+++ b/core/modules/migrate/src/MigrateExecutableInterface.php
@@ -45,6 +45,33 @@ public function processRow(Row $row, array $process = NULL, $value = NULL);
   public function getTimeLimit();
 
   /**
+   * Return an object containing current execution statistics for a migration.
+   *
+   * @return \Drupal\migrate\MigrationCounters
+   */
+  public function getCounters();
+
+  /**
+   * Increments the 'successes' counter.
+   */
+  public function incrementSuccesses();
+
+  /**
+   * Increments the 'processed' counter.
+   */
+  public function incrementProcessed();
+
+  /**
+   * Increments the 'errors' counter.
+   */
+  public function incrementErrors();
+
+  /**
+   * Increments the 'skips' counter.
+   */
+  public function incrementSkips();
+
+  /**
    * Passes messages through to the map class.
    *
    * @param string $message
diff --git a/core/modules/migrate/src/MigrationCounters.php b/core/modules/migrate/src/MigrationCounters.php
new file mode 100644
index 0000000..6c6c935
--- /dev/null
+++ b/core/modules/migrate/src/MigrationCounters.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\MigrationCounters.
+ */
+
+namespace Drupal\migrate;
+
+/**
+ * Defines the migration counters class.
+ */
+class MigrationCounters implements MigrationCountersInterface {
+
+  /**
+   * Array of counters being tracked, keyed by counter name.
+   *
+   * @var array
+   */
+  protected $counters = array();
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($counter_name) {
+    if (isset($this->counters[$counter_name])) {
+      return $this->counters[$counter_name];
+    }
+    else {
+      return 0;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function increment($counter_name) {
+    if (isset($this->counters[$counter_name])) {
+      $this->counters[$counter_name]++;
+    }
+    else {
+      $this->counters[$counter_name] = 1;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function reset($counter_name) {
+    $this->counters[$counter_name] = 0;
+  }
+
+}
diff --git a/core/modules/migrate/src/MigrationCountersInterface.php b/core/modules/migrate/src/MigrationCountersInterface.php
new file mode 100644
index 0000000..6ebd6e5
--- /dev/null
+++ b/core/modules/migrate/src/MigrationCountersInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\MigrationCountersInterface.
+ */
+
+namespace Drupal\migrate;
+
+/**
+ * Defines the migration counters interface.
+ */
+interface MigrationCountersInterface {
+
+  /**
+   * Returns the current counter value.
+   *
+   * @param string $counter_name
+   *   Name of the counter (e.g., "successes").
+   *
+   * @return int
+   *   The total for this counter, or 0 if no counter by this name exists.
+   */
+  public function get($counter_name);
+
+  /**
+   * Increments the specified counter.
+   *
+   * @param string $counter_name
+   *   Name of the counter (e.g., "successes").
+   */
+  public function increment($counter_name);
+
+  /**
+   * Resets the specified counter to 0.
+   *
+   * @param string $counter_name
+   *   Name of the counter (e.g., "successes").
+   */
+  public function reset($counter_name);
+
+}
+
diff --git a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
index f862ae8..3c82568 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
@@ -121,10 +121,12 @@ 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());
+    $counters = $this->executable->getCounters();
+    $this->assertSame(1, $counters->get('successes'));
+    $this->assertSame(1, $counters->get('processed'));
+
+    $counters->reset('successes');
+    $this->assertSame(0, $counters->get('successes'));
   }
 
   /**
@@ -171,10 +173,9 @@ 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());
+    $counters = $this->executable->getCounters();
+    $this->assertSame(1, $counters->get('successes'));
+    $this->assertSame(1, $counters->get('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
