diff --git a/kernel/include/lib/counters.h b/kernel/include/lib/counters.h
index c3b16e48eda530e3058ed9eb4881db94b1cfac29..ec63458ac146fc8dbf477058742eabed8e884d8d 100644
--- a/kernel/include/lib/counters.h
+++ b/kernel/include/lib/counters.h
@@ -37,11 +37,10 @@ __BEGIN_CDECLS
 //             "kernel.exceptions.fpu"
 //             "kernel.handles.new"
 //
-//  Reading the counters in code
-//  Don't. The counters are maintained in a per-cpu arena and
-//  atomic operations are never used to set their value so
-//  they are both imprecise and reflect only the operations
-//  on a particular core.
+// Reading the counters in code
+// Don't. The counters are maintained in a per-cpu arena and atomic
+// operations are never used to set their value so they are both
+// imprecise and reflect only the operations on a particular core.
 
 struct k_counter_desc {
     const char* name;
diff --git a/kernel/include/lib/counters_private.h b/kernel/include/lib/counters_private.h
new file mode 100644
index 0000000000000000000000000000000000000000..21d5ef29e73fc6db921911e79e2b0de23d4e6b05
--- /dev/null
+++ b/kernel/include/lib/counters_private.h
@@ -0,0 +1,22 @@
+// Copyright 2018 The Fuchsia Authors
+//
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT
+
+// These are helper routines used to implement `k counters`, exposed
+// here only for testing purposes. See the implementation for details.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <zircon/compiler.h>
+
+__BEGIN_CDECLS
+
+void counters_clean_up_values(const uint64_t* values_in, uint64_t* values_out, size_t* count_out);
+uint64_t counters_get_percentile(const uint64_t* values, size_t count, uint64_t percentage_dot8);
+bool counters_has_outlier(const uint64_t* values_in);
+
+__END_CDECLS
diff --git a/kernel/lib/counters/counters.cpp b/kernel/lib/counters/counters.cpp
index 74398c5ce410d8ecd1122419e31b7a642a65d55d..0713599f3c881fd3789f7fa943e3bca428fc3e60 100644
--- a/kernel/lib/counters/counters.cpp
+++ b/kernel/lib/counters/counters.cpp
@@ -6,6 +6,7 @@
 
 #include <lib/counters.h>
 
+#include <stdlib.h>
 #include <string.h>
 
 #include <arch/ops.h>
@@ -23,6 +24,7 @@
 #include <lk/init.h>
 
 #include <lib/console.h>
+#include <lib/counters_private.h>
 
 // The arena is allocated in kernel.ld linker script.
 extern int64_t kcounters_arena[];
@@ -77,7 +79,74 @@ static void counters_init(unsigned level) {
     }
 }
 
-static void dump_counter(const k_counter_desc* desc) {
+// Collapse values to only non-zero ones and sort.
+void counters_clean_up_values(const uint64_t* values_in, uint64_t* values_out, size_t* count_out) {
+  assert(values_in != values_out);
+
+  *count_out = 0;
+  for (size_t i = 0; i < SMP_MAX_CPUS; ++i) {
+    if (values_in[i] > 0) {
+      values_out[(*count_out)++] = values_in[i];
+    }
+  }
+
+  qsort(values_out, *count_out, sizeof(uint64_t), [](const void* a, const void* b) {
+      return (*((uint64_t*)a) > *((uint64_t*)b)) - (*((uint64_t*)a) < *((uint64_t*)b));
+  });
+}
+
+static constexpr uint64_t DOT8_SHIFT = 8;
+
+// This calculation tries to match what sheets.google.com uses for QUARTILE()
+// and PERCENTAGE(), however this uses 56.8 rather than floating point.
+// https://en.wikipedia.org/wiki/Percentile#The_linear_interpolation_between_closest_ranks_method
+// This is just a linear interpolation between the items bracketing that
+// percentage. The input value array must be sorted on entry to this function.
+uint64_t counters_get_percentile(const uint64_t* values, size_t count, uint64_t percentage_dot8) {
+    assert(count >= 2);
+
+    uint64_t target_dot8 = (count - 1) * percentage_dot8;
+    uint64_t low_index = target_dot8 >> DOT8_SHIFT;
+    uint64_t high_index = low_index + 1;
+    uint64_t fraction_dot8 = target_dot8 & 0xff;
+
+    uint64_t delta = values[high_index] - values[low_index];
+    return ((values[low_index] << DOT8_SHIFT) + fraction_dot8 * delta);
+}
+
+bool counters_has_outlier(const uint64_t* values_in) {
+    uint64_t values[SMP_MAX_CPUS];
+    size_t count;
+    counters_clean_up_values(values_in, values, &count);
+    if (count < 2)
+        return false;
+
+    // If there's a value that's an outlier per
+    // https://en.wikipedia.org/wiki/Outlier#Tukey's_fences, then we deem it
+    // worth outputting the per-core values. This is not perfect, but it is
+    // somewhat tricky to determine outliers for small data sets. We typically
+    // have something like 4 cores here, and e.g. calculating standard deviation
+    // is not useful for so few values.
+    const uint64_t q1_dot8 = counters_get_percentile(values, count, /*0.25*/ 64);
+    const uint64_t q3_dot8 = counters_get_percentile(values, count, /*0.75*/ 192);
+    const uint64_t k_dot8 = /*1.5*/ 384;
+    const uint64_t q_delta_dot8 = q3_dot8 - q1_dot8;
+    const int64_t low_dot8 =
+        q1_dot8 - static_cast<int64_t>(((k_dot8 * q_delta_dot8) >> DOT8_SHIFT));
+    const int64_t high_dot8 =
+        q3_dot8 + static_cast<int64_t>(((k_dot8 * q_delta_dot8) >> DOT8_SHIFT));
+
+    for (size_t i = 0; i < count; ++i) {
+      if ((static_cast<int64_t>(values[i]) << DOT8_SHIFT) < low_dot8 ||
+          (static_cast<int64_t>(values[i]) << DOT8_SHIFT) > high_dot8) {
+        return true;
+      }
+  }
+
+  return false;
+}
+
+static void dump_counter(const k_counter_desc* desc, bool verbose) {
     size_t counter_index = kcounter_index(desc);
 
     uint64_t sum = 0;
@@ -93,23 +162,28 @@ static void dump_counter(const k_counter_desc* desc) {
     if (sum == 0u)
         return;
 
-    // Print the per-core counts when the sum is not zero.
-    printf("     ");
-    for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) {
-        if (values[ix] > 0)
-            printf("[%zu:%lu]", ix, values[ix]);
+    // Print the per-core counts if verbose (-v) is set and it's not zero, or if
+    // a value for one of the cores indicates it's an outlier.
+    if (verbose || counters_has_outlier(values)) {
+        printf("     ");
+        for (size_t ix = 0; ix != SMP_MAX_CPUS; ++ix) {
+            if (values[ix] > 0) {
+                printf("[%zu:%lu]", ix, values[ix]);
+            }
+        }
+        printf("\n");
     }
-    printf("\n");
 }
 
-static void dump_all_counters() {
+static void dump_all_counters(bool verbose) {
     printf("%zu counters available:\n", get_num_counters());
     for (auto it = kcountdesc_begin; it != kcountdesc_end; ++it) {
-        dump_counter(it);
+        dump_counter(it, verbose);
     }
 }
 
 static int watcher_thread_fn(void* arg) {
+    bool verbose = static_cast<bool>(reinterpret_cast<uintptr_t>(arg));
     while (true) {
         {
             fbl::AutoLock lock(&watcher_lock);
@@ -120,7 +194,7 @@ static int watcher_thread_fn(void* arg) {
 
             watched_counter_t* wc;
             list_for_every_entry (&watcher_list, wc, watched_counter_t, node) {
-                dump_counter(wc->desc);
+                dump_counter(wc->desc, verbose);
             }
         }
 
@@ -129,9 +203,18 @@ static int watcher_thread_fn(void* arg) {
 }
 
 static int view_counter(int argc, const cmd_args* argv) {
+    bool verbose = false;
+    if (argc == 3) {
+        if (strcmp(argv[1].str, "-v") == 0) {
+            verbose = true;
+            argc--;
+            argv++;
+        }
+    }
+
     if (argc == 2) {
-        if (strcmp(argv[1].str, "--all") == 0) {
-            dump_all_counters();
+        if (strcmp(argv[1].str, "all") == 0) {
+            dump_all_counters(verbose);
         } else {
             int num_results = 0;
             auto name = argv[1].str;
@@ -139,21 +222,21 @@ static int view_counter(int argc, const cmd_args* argv) {
             while (desc != kcountdesc_end) {
                 if (!prefix_match(name, desc->name))
                     break;
-                dump_counter(desc);
+                dump_counter(desc, verbose);
                 ++num_results;
                 ++desc;
             }
             if (num_results == 0) {
-                printf("counter '%s' not found, try --all\n", name);
+                printf("counter '%s' not found, try all\n", name);
             } else {
                 printf("%d counters found\n", num_results);
             }
         }
     } else {
         printf(
-            "counters view <counter-name>\n"
-            "counters view <counter-prefix>\n"
-            "counters view --all\n"
+            "counters view [-v] <counter-name>\n"
+            "counters view [-v] <counter-prefix>\n"
+            "counters view [-v] all\n"
         );
         return 1;
     }
@@ -162,8 +245,17 @@ static int view_counter(int argc, const cmd_args* argv) {
 }
 
 static int watch_counter(int argc, const cmd_args* argv) {
+    bool verbose = false;
+    if (argc == 3) {
+        if (strcmp(argv[1].str, "-v") == 0) {
+            verbose = true;
+            argc--;
+            argv++;
+        }
+    }
+
     if (argc == 2) {
-        if (strcmp(argv[1].str, "--stop") == 0) {
+        if (strcmp(argv[1].str, "stop") == 0) {
             fbl::AutoLock lock(&watcher_lock);
             watched_counter_t* wc;
             while ((wc = list_remove_head_type(
@@ -198,7 +290,8 @@ static int watch_counter(int argc, const cmd_args* argv) {
             list_add_head(&watcher_list, &wc->node);
             if (watcher_thread == nullptr) {
                 watcher_thread = thread_create(
-                    "counter-watcher", watcher_thread_fn, nullptr, LOW_PRIORITY);
+                    "counter-watcher", watcher_thread_fn,
+                    reinterpret_cast<void*>(static_cast<uintptr_t>(verbose)), LOW_PRIORITY);
                 if (watcher_thread == nullptr) {
                     printf("no memory for watcher thread\n");
                     return 1;
@@ -209,8 +302,8 @@ static int watch_counter(int argc, const cmd_args* argv) {
 
     } else {
         printf(
-            "counters watch <counter-id>\n"
-            "counters watch --stop\n"
+            "counters watch [-v] <counter-id>\n"
+            "counters watch stop\n"
         );
     }
 
@@ -229,8 +322,8 @@ static int cmd_counters(int argc, const cmd_args* argv, uint32_t flags) {
 
     printf(
         "inspect system counters:\n"
-        "  counters view <name>\n"
-        "  counters watch <id>\n"
+        "  counters view [-v] <name>\n"
+        "  counters watch [-v] <id>\n"
     );
     return 0;
 }
diff --git a/kernel/lib/counters/counters_tests.cpp b/kernel/lib/counters/counters_tests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3770ef0453cad0e7a08c3c37f828f41b870d52a3
--- /dev/null
+++ b/kernel/lib/counters/counters_tests.cpp
@@ -0,0 +1,81 @@
+// Copyright 2018 The Fuchsia Authors
+//
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT
+
+#include <lib/counters_private.h>
+
+#include <lib/unittest/unittest.h>
+
+static bool value_cleanup() {
+    BEGIN_TEST;
+
+    uint64_t outputs[SMP_MAX_CPUS];
+    size_t out_count;
+
+    // Sorted.
+    uint64_t inputs0[SMP_MAX_CPUS] = { 13, 4, 8, 9 };
+    counters_clean_up_values(inputs0, outputs, &out_count);
+    ASSERT_EQ(out_count, 4u, "");
+    EXPECT_EQ(outputs[0], 4u, "");
+    EXPECT_EQ(outputs[1], 8u, "");
+    EXPECT_EQ(outputs[2], 9u, "");
+    EXPECT_EQ(outputs[3], 13u, "");
+
+    // Collapsed to remove zeros.
+    uint64_t inputs1[SMP_MAX_CPUS] = { 13, 0, 0, 9 };
+    counters_clean_up_values(inputs1, outputs, &out_count);
+    ASSERT_EQ(out_count, 2u, "");
+    EXPECT_EQ(outputs[0], 9u, "");
+    EXPECT_EQ(outputs[1], 13u, "");
+
+    END_TEST;
+}
+
+// Data to compare vs. results in
+// https://docs.google.com/spreadsheets/d/1D58chwOpO-3_c41NMGJkpmFuOpGSYH-W50bD6MdOAjo/edit?usp=sharing
+static uint64_t test_counters_inputs0[SMP_MAX_CPUS] = {5105, 4602, 4031, 4866};
+static uint64_t test_counters_inputs1[SMP_MAX_CPUS] = {3524, 3461, 3567, 2866};
+
+static bool percentile_determination() {
+  BEGIN_TEST;
+
+  uint64_t cleaned[SMP_MAX_CPUS];
+  size_t out_count;
+
+  counters_clean_up_values(test_counters_inputs0, cleaned, &out_count);
+  EXPECT_EQ(counters_get_percentile(cleaned, out_count, /*0.25*/ 64),
+            /* 4459.25 */ (4459u << 8) + 64u, "");
+  EXPECT_EQ(counters_get_percentile(cleaned, out_count, /*0.75*/ 192),
+            /* 4925.75 */ (4925u << 8) + 192u, "");
+
+  counters_clean_up_values(test_counters_inputs1, cleaned, &out_count);
+  EXPECT_EQ(counters_get_percentile(cleaned, out_count, /*0.25*/ 64),
+            /* 3312.25 */ (3312u << 8) + 64u, "");
+  EXPECT_EQ(counters_get_percentile(cleaned, out_count, /*0.75*/ 192),
+            /* 3534.75 */ (3534u << 8) + 192u, "");
+
+  END_TEST;
+}
+
+static bool outlier_check() {
+  BEGIN_TEST;
+
+  uint64_t no_values[SMP_MAX_CPUS] = {0};
+  EXPECT_FALSE(counters_has_outlier(no_values), "0 values shouldn't have outlier");
+
+  uint64_t one_value[SMP_MAX_CPUS] = {789};
+  EXPECT_FALSE(counters_has_outlier(one_value), "1 value shouldn't have outlier");
+
+  EXPECT_FALSE(counters_has_outlier(test_counters_inputs0), "");
+  EXPECT_TRUE(counters_has_outlier(test_counters_inputs1), "");
+
+  END_TEST;
+}
+
+UNITTEST_START_TESTCASE(counters_tests)
+UNITTEST("value cleanup", value_cleanup)
+UNITTEST("percentile determination", percentile_determination)
+UNITTEST("outlier check", outlier_check)
+UNITTEST_END_TESTCASE(counters_tests, "counters_tests", "Counters tests");
diff --git a/kernel/lib/counters/rules.mk b/kernel/lib/counters/rules.mk
index b1a8920aefc0ae44517f0ddc1e65c690d903cc82..96cec0026ec4129f063bf8fc32a27afc4f330cf4 100644
--- a/kernel/lib/counters/rules.mk
+++ b/kernel/lib/counters/rules.mk
@@ -9,9 +9,11 @@ LOCAL_DIR := $(GET_LOCAL_DIR)
 MODULE := $(LOCAL_DIR)
 
 MODULE_SRCS += \
-	$(LOCAL_DIR)/counters.cpp
+	$(LOCAL_DIR)/counters.cpp \
+	$(LOCAL_DIR)/counters_tests.cpp
 
 MODULE_DEPS += \
-	kernel/lib/console
+	kernel/lib/console \
+	kernel/lib/unittest
 
 include make/module.mk