From a78a870d6beb923206383dbcf35809d3065f1ebc Mon Sep 17 00:00:00 2001
From: Doug Evans <dje@google.com>
Date: Tue, 14 May 2019 00:25:27 +0000
Subject: [PATCH] [ktrace_provider] Add test harness

... and context-switch tests

PT-158 #done

Change-Id: I5023f7cfbb073ae7999fcdcd59d2d9370be75963
---
 garnet/bin/ktrace_provider/BUILD.gn           |  61 ++++-
 garnet/bin/ktrace_provider/app.cc             |  11 +-
 garnet/bin/ktrace_provider/device_reader.cc   |  45 ++++
 garnet/bin/ktrace_provider/device_reader.h    |  33 +++
 garnet/bin/ktrace_provider/importer.cc        |  10 +-
 garnet/bin/ktrace_provider/importer.h         |  19 +-
 .../bin/ktrace_provider/importer_unittest.cc  | 228 ++++++++++++++++++
 .../ktrace_provider/meta/importer_tests.cmx   |   5 +
 garnet/bin/ktrace_provider/reader.cc          |  60 ++---
 garnet/bin/ktrace_provider/reader.h           |  26 +-
 garnet/bin/ktrace_provider/tags.cc            |   2 +
 garnet/bin/ktrace_provider/tags.h             |   4 +-
 garnet/bin/ktrace_provider/test_reader.cc     |  20 ++
 garnet/bin/ktrace_provider/test_reader.h      |  26 ++
 garnet/packages/tests/BUILD.gn                |   1 +
 15 files changed, 474 insertions(+), 77 deletions(-)
 create mode 100644 garnet/bin/ktrace_provider/device_reader.cc
 create mode 100644 garnet/bin/ktrace_provider/device_reader.h
 create mode 100644 garnet/bin/ktrace_provider/importer_unittest.cc
 create mode 100644 garnet/bin/ktrace_provider/meta/importer_tests.cmx
 create mode 100644 garnet/bin/ktrace_provider/test_reader.cc
 create mode 100644 garnet/bin/ktrace_provider/test_reader.h

diff --git a/garnet/bin/ktrace_provider/BUILD.gn b/garnet/bin/ktrace_provider/BUILD.gn
index e96bfb70a0e..614fdab9786 100644
--- a/garnet/bin/ktrace_provider/BUILD.gn
+++ b/garnet/bin/ktrace_provider/BUILD.gn
@@ -3,6 +3,25 @@
 # found in the LICENSE file.
 
 import("//build/package.gni")
+import("//build/test/test_package.gni")
+
+source_set("importer") {
+  sources = [
+    "importer.cc",
+    "importer.h",
+    "reader.cc",
+    "reader.h",
+    "tags.cc",
+    "tags.h",
+  ]
+
+  public_deps = [
+    "//src/lib/fxl",
+    "//zircon/public/lib/fbl",
+    "//zircon/public/lib/trace-engine",
+    "//zircon/public/lib/zircon-internal",
+  ]
+}
 
 executable("bin") {
   output_name = "ktrace_provider"
@@ -10,18 +29,15 @@ executable("bin") {
   sources = [
     "app.cc",
     "app.h",
-    "importer.cc",
-    "importer.h",
+    "device_reader.cc",
+    "device_reader.h",
     "log_importer.cc",
     "log_importer.h",
     "main.cc",
-    "reader.cc",
-    "reader.h",
-    "tags.cc",
-    "tags.h",
   ]
 
   deps = [
+    ":importer",
     "//sdk/lib/sys/cpp",
     "//src/lib/fxl",
     "//zircon/public/fidl/fuchsia-tracing-kernel:fuchsia-tracing-kernel_c",
@@ -47,3 +63,36 @@ package("ktrace_provider") {
     },
   ]
 }
+
+executable("importer_test_app") {
+  testonly = true
+  output_name = "importer_tests"
+
+  sources = [
+    "importer_unittest.cc",
+    "test_reader.cc",
+    "test_reader.h",
+  ]
+
+  deps = [
+    ":importer",
+    "//src/lib/fxl",
+    "//third_party/googletest:gtest",
+    "//zircon/public/lib/fbl",
+    "//zircon/public/lib/trace-engine",
+    "//zircon/public/lib/trace-test-utils",
+  ]
+}
+
+test_package("ktrace_provider_tests") {
+  deps = [
+    ":importer_test_app"
+  ]
+
+  tests = [
+    {
+      name = "importer_tests"
+      environments = basic_envs
+    },
+  ]
+}
diff --git a/garnet/bin/ktrace_provider/app.cc b/garnet/bin/ktrace_provider/app.cc
index 48bc821dc6c..d3e5d9ac213 100644
--- a/garnet/bin/ktrace_provider/app.cc
+++ b/garnet/bin/ktrace_provider/app.cc
@@ -5,21 +5,22 @@
 #include "garnet/bin/ktrace_provider/app.h"
 
 #include <fcntl.h>
+#include <unistd.h>
+
 #include <fuchsia/tracing/kernel/c/fidl.h>
 #include <lib/async/default.h>
 #include <lib/fdio/fdio.h>
 #include <lib/zx/channel.h>
+#include <src/lib/fxl/arraysize.h>
+#include <src/lib/fxl/logging.h>
 #include <trace-engine/instrumentation.h>
 #include <trace-provider/provider.h>
-#include <unistd.h>
 #include <zircon/device/ktrace.h>
 #include <zircon/status.h>
 #include <zircon/syscalls/log.h>
 
+#include "garnet/bin/ktrace_provider/device_reader.h"
 #include "garnet/bin/ktrace_provider/importer.h"
-#include "garnet/bin/ktrace_provider/reader.h"
-#include "src/lib/fxl/arraysize.h"
-#include "src/lib/fxl/logging.h"
 
 namespace ktrace_provider {
 namespace {
@@ -191,7 +192,7 @@ void App::StopKTrace() {
   // Acquire a context for writing to the trace buffer.
   auto buffer_context = trace_acquire_context();
 
-  Reader reader;
+  DeviceReader reader;
   Importer importer(buffer_context);
   if (!importer.Import(reader)) {
     FXL_LOG(ERROR) << "Errors encountered while importing ktrace data";
diff --git a/garnet/bin/ktrace_provider/device_reader.cc b/garnet/bin/ktrace_provider/device_reader.cc
new file mode 100644
index 00000000000..6d4ded832bb
--- /dev/null
+++ b/garnet/bin/ktrace_provider/device_reader.cc
@@ -0,0 +1,45 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "garnet/bin/ktrace_provider/device_reader.h"
+
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "src/lib/files/eintr_wrapper.h"
+
+namespace ktrace_provider {
+
+namespace {
+constexpr char kTraceDev[] = "/dev/misc/ktrace";
+}  // namespace
+
+DeviceReader::DeviceReader()
+  : Reader(buffer_, kChunkSize),
+    fd_(open(kTraceDev, O_RDONLY)) {
+}
+
+void DeviceReader::ReadMoreData() {
+  memcpy(buffer_, current_, AvailableBytes());
+  char* new_marker = buffer_ + AvailableBytes();
+
+  while (new_marker < end_) {
+    int bytes_read =
+        HANDLE_EINTR(read(fd_.get(), new_marker,
+                          std::distance(const_cast<const char*>(new_marker),
+                                        end_)));
+
+    if (bytes_read <= 0)
+      break;
+
+    new_marker += bytes_read;
+  }
+
+  marker_ = new_marker;
+  current_ = buffer_;
+}
+
+}  // namespace ktrace_provider
diff --git a/garnet/bin/ktrace_provider/device_reader.h b/garnet/bin/ktrace_provider/device_reader.h
new file mode 100644
index 00000000000..eb1aed68841
--- /dev/null
+++ b/garnet/bin/ktrace_provider/device_reader.h
@@ -0,0 +1,33 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_KTRACE_PROVIDER_DEVICE_READER_H_
+#define GARNET_BIN_KTRACE_PROVIDER_DEVICE_READER_H_
+
+#include <src/lib/files/unique_fd.h>
+#include <src/lib/fxl/macros.h>
+
+#include "garnet/bin/ktrace_provider/reader.h"
+
+namespace ktrace_provider {
+
+class DeviceReader : public Reader {
+ public:
+  DeviceReader();
+
+ private:
+  static constexpr size_t kChunkSize{16 * 4 * 1024};
+
+  void ReadMoreData() override;
+
+  fxl::UniqueFD fd_;
+
+  char buffer_[kChunkSize];
+
+  FXL_DISALLOW_COPY_AND_ASSIGN(DeviceReader);
+};
+
+}  // namespace ktrace_provider
+
+#endif  // GARNET_BIN_KTRACE_PROVIDER_DEVICE_READER_H_
diff --git a/garnet/bin/ktrace_provider/importer.cc b/garnet/bin/ktrace_provider/importer.cc
index b728ec769c1..0d22312890a 100644
--- a/garnet/bin/ktrace_provider/importer.cc
+++ b/garnet/bin/ktrace_provider/importer.cc
@@ -15,13 +15,6 @@
 namespace ktrace_provider {
 namespace {
 
-constexpr zx_koid_t kNoProcess = 0u;
-constexpr zx_koid_t kKernelThreadFlag = 0x100000000;
-
-constexpr zx_koid_t kKernelPseudoKoidBase = 0x00000000'70000000u;
-constexpr zx_koid_t kKernelPseudoCpuBase =
-    kKernelPseudoKoidBase + 0x00000000'01000000u;
-
 constexpr uint64_t ToUInt64(uint32_t lo, uint32_t hi) {
   return (static_cast<uint64_t>(hi) << 32) | lo;
 }
@@ -96,7 +89,8 @@ bool Importer::Import(Reader& reader) {
   while (true) {
     if (auto record = reader.ReadNextRecord()) {
       if (!ImportRecord(record, KTRACE_LEN(record->tag))) {
-        FXL_VLOG(2) << "Skipped ktrace record, tag=" << record->tag;
+        FXL_VLOG(2) << "Skipped ktrace record, tag=0x" << std::hex
+                    << record->tag;
       }
     } else {
       break;
diff --git a/garnet/bin/ktrace_provider/importer.h b/garnet/bin/ktrace_provider/importer.h
index a4db5343498..be0d61df3f8 100644
--- a/garnet/bin/ktrace_provider/importer.h
+++ b/garnet/bin/ktrace_provider/importer.h
@@ -5,19 +5,21 @@
 #ifndef GARNET_BIN_KTRACE_PROVIDER_IMPORTER_H_
 #define GARNET_BIN_KTRACE_PROVIDER_IMPORTER_H_
 
-#include <fbl/string.h>
-#include <fbl/string_piece.h>
-#include <trace-engine/context.h>
+#include <stddef.h>
+#include <stdint.h>
 
 #include <array>
 #include <tuple>
 #include <unordered_map>
 #include <vector>
 
+#include <fbl/string.h>
+#include <fbl/string_piece.h>
+#include <trace-engine/context.h>
+
+#include "garnet/bin/ktrace_provider/reader.h"
 #include "garnet/bin/ktrace_provider/tags.h"
 #include "src/lib/fxl/macros.h"
-#include "stddef.h"
-#include "stdint.h"
 
 namespace ktrace_provider {
 
@@ -25,6 +27,13 @@ class Reader;
 
 class Importer {
  public:
+  static constexpr zx_koid_t kNoProcess = 0u;
+  static constexpr zx_koid_t kKernelThreadFlag = 0x100000000;
+
+  static constexpr zx_koid_t kKernelPseudoKoidBase = 0x00000000'70000000u;
+  static constexpr zx_koid_t kKernelPseudoCpuBase =
+      kKernelPseudoKoidBase + 0x00000000'01000000u;
+
   Importer(trace_context* context);
   ~Importer();
 
diff --git a/garnet/bin/ktrace_provider/importer_unittest.cc b/garnet/bin/ktrace_provider/importer_unittest.cc
new file mode 100644
index 00000000000..326f792098a
--- /dev/null
+++ b/garnet/bin/ktrace_provider/importer_unittest.cc
@@ -0,0 +1,228 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fbl/algorithm.h>
+#include <gtest/gtest.h>
+#include <src/lib/fxl/command_line.h>
+#include <src/lib/fxl/log_settings_command_line.h>
+#include <trace-engine/instrumentation.h>
+#include <trace-test-utils/fixture.h>
+
+#include "garnet/bin/ktrace_provider/importer.h"
+#include "garnet/bin/ktrace_provider/test_reader.h"
+
+namespace ktrace_provider {
+namespace {
+
+class TestImporter : public ::testing::Test {
+ public:
+  // A copy of kernel/thread.h:thread_state values we use.
+  enum class KernelThreadState : uint8_t {
+    // The naming style chosen here is to be consistent with thread.h.
+    // If its values change, just re-cut-n-paste.
+    THREAD_INITIAL = 0,
+    THREAD_READY,
+    THREAD_RUNNING,
+    THREAD_BLOCKED,
+    THREAD_BLOCKED_READ_LOCK,
+    THREAD_SLEEPING,
+    THREAD_SUSPENDED,
+    THREAD_DEATH,
+  };
+
+  void SetUp() {
+    fixture_set_up(kNoAttachToThread, TRACE_BUFFERING_MODE_ONESHOT,
+                   kFxtBufferSize);
+    fixture_start_tracing();
+    context_ = trace_acquire_context();
+    ASSERT_NE(context_, nullptr);
+  }
+
+  void StopTracing() {
+    if (context_) {
+      trace_release_context(context_);
+      context_ = nullptr;
+    }
+    fixture_stop_tracing();
+  }
+
+  void TearDown() {
+    // Stop tracing maybe again just in case.
+    StopTracing();
+    fixture_tear_down();
+  }
+
+  // Extract the records in the buffer, discarding administrative records
+  // that the importer creates.
+  // TODO(dje): Use std::vector when fixture is ready.
+  bool ExtractRecords(fbl::Vector<trace::Record>* out_records) {
+    fbl::Vector<trace::Record> records;
+
+    if (!fixture_read_records(&records)) {
+      return false;
+    }
+
+    // The kernel process record is the last administrative record.
+    // Drop every record up to and including that one.
+    bool skipping = true;
+    for (auto& rec : records) {
+      if (skipping) {
+        if (rec.type() == trace::RecordType::kKernelObject) {
+          const trace::Record::KernelObject& kobj = rec.GetKernelObject();
+          if (kobj.object_type == ZX_OBJ_TYPE_PROCESS &&
+              kobj.koid == 0u &&
+              kobj.name == "kernel") {
+            skipping = false;
+          }
+        }
+      } else {
+        out_records->push_back(std::move(rec));
+      }
+    }
+
+    // We should have found the kernel process record.
+    if (skipping) {
+      FXL_VLOG(1) << "Kernel process record not found";
+      return false;
+    }
+
+    return true;
+  }
+
+  void CompareRecord(const trace::Record& rec, const char* expected) {
+    EXPECT_STREQ(rec.ToString().c_str(), expected);
+  }
+
+  void EmitKtraceRecord(const void* record, size_t record_size) {
+    ASSERT_LE(record_size, KtraceAvailableBytes());
+    memcpy(ktrace_buffer_next_, record, record_size);
+    ktrace_buffer_next_ += record_size;
+  }
+
+  void EmitKtrace32Record(uint32_t tag, uint32_t tid, uint64_t ts,
+                          uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
+    const ktrace_rec_32b record {
+      .tag = tag,
+      .tid = tid,
+      .ts = ts,
+      .a = a,
+      .b = b,
+      .c = c,
+      .d = d,
+    };
+    EmitKtraceRecord(&record, sizeof(record));
+  }
+
+  void EmitContextSwitchRecord(uint64_t ts, uint32_t old_thread_tid,
+                               uint32_t new_thread_tid, uint8_t cpu,
+                               KernelThreadState old_thread_state,
+                               uint8_t old_thread_prio,
+                               uint8_t new_thread_prio,
+                               uint32_t new_kernel_thread) {
+    uint32_t old_kernel_thread = 0;  // importer ignores this
+    EmitKtrace32Record(TAG_CONTEXT_SWITCH, old_thread_tid, ts,
+                       new_thread_tid,
+                       (cpu |
+                        (static_cast<uint8_t>(old_thread_state) << 8) |
+                        (old_thread_prio << 16) | (new_thread_prio << 24)),
+                       old_kernel_thread, new_kernel_thread);
+  }
+
+  bool StopTracingAndImportRecords(fbl::Vector<trace::Record>* out_records) {
+    TestReader reader{ktrace_buffer(), ktrace_buffer_written()};
+    Importer importer{context()};
+
+    if (!importer.Import(reader)) {
+      return false;
+    }
+
+    // Do this after importing as the importer needs tracing to be running in
+    // order to acquire a "context" with which to write records.
+    StopTracing();
+
+    return ExtractRecords(out_records);
+  }
+
+  trace_context_t* context() const { return context_; }
+
+  const char* ktrace_buffer() const { return ktrace_buffer_; }
+
+  size_t ktrace_buffer_written() const {
+    return ktrace_buffer_next_ - ktrace_buffer_;
+  }
+
+ private:
+  static constexpr size_t kKtraceBufferSize = 65536;
+  static constexpr size_t kFxtBufferSize = 65536;
+
+  size_t KtraceAvailableBytes() {
+    return std::distance(ktrace_buffer_next_, ktrace_buffer_end_);
+  }
+
+  char ktrace_buffer_[kKtraceBufferSize];
+  char* ktrace_buffer_next_ = ktrace_buffer_;
+  char* ktrace_buffer_end_ = ktrace_buffer_ + kKtraceBufferSize;
+  trace_context_t* context_ = nullptr;
+};
+
+TEST_F(TestImporter, ContextSwitch) {
+  // Establish initial running thread.
+  EmitContextSwitchRecord(
+      99,  // ts
+      0,  // old_thread_tid
+      42, // new_thread_tid
+      1,  // cpu
+      KernelThreadState::THREAD_RUNNING, // old_thread_state
+      3,  // old_thread_prio
+      4,  // new_thread_prio
+      0);
+  // Test switching to user thread.
+  EmitContextSwitchRecord(
+      100,  // ts
+      42,  // old_thread_tid
+      43, // new_thread_tid
+      1,  // cpu
+      KernelThreadState::THREAD_RUNNING, // old_thread_state
+      5,  // old_thread_prio
+      6,  // new_thread_prio
+      0);
+  // Test switching to kernel thread.
+  EmitContextSwitchRecord(
+      101,  // ts
+      43,  // old_thread_tid
+      0,   // 0 --> kernel thread
+      1,  // cpu
+      KernelThreadState::THREAD_RUNNING, // old_thread_state
+      7,  // old_thread_prio
+      8,  // new_thread_prio
+      12345678);
+  static const char* const expected[] = {
+      "ContextSwitch(ts: 99, cpu: 1, os: running, opt: 0/0, ipt: 0/42, oprio: 3, iprio: 4)",
+      "ContextSwitch(ts: 100, cpu: 1, os: running, opt: 0/42, ipt: 0/43, oprio: 5, iprio: 6)",
+      // 4307312974 = 12345678 | kKernelThreadFlag
+      "ContextSwitch(ts: 101, cpu: 1, os: running, opt: 0/43, ipt: 0/4307312974, oprio: 7, iprio: 8)",
+  };
+
+  fbl::Vector<trace::Record> records;
+  ASSERT_TRUE(StopTracingAndImportRecords(&records));
+  ASSERT_EQ(records.size(), fbl::count_of(expected));
+  for (size_t i = 0; i < records.size(); ++i) {
+    CompareRecord(records[i], expected[i]);
+  }
+}
+
+}  // namespace
+}  // namespace ktrace_provider
+
+// Provide our own main so that --verbose,etc. are recognized.
+int main(int argc, char** argv) {
+  auto cl = fxl::CommandLineFromArgcArgv(argc, argv);
+  if (!fxl::SetLogSettingsFromCommandLine(cl)) {
+    return EXIT_FAILURE;
+  }
+
+  testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}
diff --git a/garnet/bin/ktrace_provider/meta/importer_tests.cmx b/garnet/bin/ktrace_provider/meta/importer_tests.cmx
new file mode 100644
index 00000000000..fd7604d7207
--- /dev/null
+++ b/garnet/bin/ktrace_provider/meta/importer_tests.cmx
@@ -0,0 +1,5 @@
+{
+    "program": {
+        "binary": "test/importer_tests"
+    }
+}
diff --git a/garnet/bin/ktrace_provider/reader.cc b/garnet/bin/ktrace_provider/reader.cc
index 7d71e61f596..9a6a68964da 100644
--- a/garnet/bin/ktrace_provider/reader.cc
+++ b/garnet/bin/ktrace_provider/reader.cc
@@ -2,62 +2,50 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "garnet/bin/ktrace_provider/reader.h"
-
-#include <fcntl.h>
-#include <limits.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#include <lib/zircon-internal/ktrace.h>
 
-#include "src/lib/files/eintr_wrapper.h"
+#include "garnet/bin/ktrace_provider/reader.h"
 
 namespace ktrace_provider {
-namespace {
-
-constexpr char kTraceDev[] = "/dev/misc/ktrace";
-
-}  // namespace
 
-Reader::Reader() : fd_(open(kTraceDev, O_RDONLY)) {}
+Reader::Reader(const char* buffer, size_t buffer_size)
+  : current_(buffer),
+    marker_(buffer),
+    end_(buffer + buffer_size) {
+}
 
-ktrace_header_t* Reader::ReadNextRecord() {
-  if (AvailableBytes() < sizeof(ktrace_header_t))
+const ktrace_header_t* Reader::ReadNextRecord() {
+  if (AvailableBytes() < sizeof(ktrace_header_t)) {
     ReadMoreData();
+  }
 
-  if (AvailableBytes() < sizeof(ktrace_header_t))
+  if (AvailableBytes() < sizeof(ktrace_header_t)) {
+    FXL_VLOG(10) << "No more records";
     return nullptr;
+  }
 
-  auto record = reinterpret_cast<ktrace_header_t*>(current_);
+  auto record = reinterpret_cast<const ktrace_header_t*>(current_);
 
-  if (AvailableBytes() < KTRACE_LEN(record->tag))
+  if (AvailableBytes() < KTRACE_LEN(record->tag)) {
     ReadMoreData();
+  }
 
-  if (AvailableBytes() < KTRACE_LEN(record->tag))
+  if (AvailableBytes() < KTRACE_LEN(record->tag)) {
+    FXL_VLOG(10) << "No more records, incomplete last record";
     return nullptr;
+  }
 
-  record = reinterpret_cast<ktrace_header_t*>(current_);
+  record = reinterpret_cast<const ktrace_header_t*>(current_);
   current_ += KTRACE_LEN(record->tag);
 
   number_bytes_read_ += KTRACE_LEN(record->tag);
   number_records_read_ += 1;
-  return record;
-}
-
-void Reader::ReadMoreData() {
-  memcpy(buffer_, current_, AvailableBytes());
-  marker_ = buffer_ + AvailableBytes();
 
-  while (marker_ < end_) {
-    int bytes_read =
-        HANDLE_EINTR(read(fd_.get(), marker_, std::distance(marker_, end_)));
+  FXL_VLOG(10) << "Importing ktrace event 0x" << std::hex
+               << KTRACE_EVENT(record->tag) << ", size " << std::dec
+               << KTRACE_LEN(record->tag);
 
-    if (bytes_read <= 0)
-      break;
-
-    marker_ += bytes_read;
-  }
-
-  current_ = buffer_;
+  return record;
 }
 
 }  // namespace ktrace_provider
diff --git a/garnet/bin/ktrace_provider/reader.h b/garnet/bin/ktrace_provider/reader.h
index 275945e1cc4..5de6731cd1e 100644
--- a/garnet/bin/ktrace_provider/reader.h
+++ b/garnet/bin/ktrace_provider/reader.h
@@ -5,36 +5,34 @@
 #ifndef GARNET_BIN_KTRACE_PROVIDER_READER_H_
 #define GARNET_BIN_KTRACE_PROVIDER_READER_H_
 
-#include <lib/zircon-internal/ktrace.h>
+#include <iterator>
 
-#include "src/lib/files/unique_fd.h"
-#include "src/lib/fxl/macros.h"
+#include <src/lib/files/unique_fd.h>
+#include <src/lib/fxl/macros.h>
+#include <lib/zircon-internal/ktrace.h>
 
 namespace ktrace_provider {
 
 class Reader {
  public:
-  Reader();
+  Reader(const char* buffer, size_t buffer_size);
+  virtual ~Reader() = default;
 
-  ktrace_header_t* ReadNextRecord();
+  const ktrace_header_t* ReadNextRecord();
 
   size_t number_bytes_read() const { return number_bytes_read_; }
   size_t number_records_read() const { return number_records_read_; }
 
- private:
-  static constexpr size_t kChunkSize{16 * 4 * 1024};
-
+ protected:
   inline size_t AvailableBytes() const {
     return std::distance(current_, marker_);
   }
 
-  void ReadMoreData();
+  virtual void ReadMoreData() = 0;
 
-  fxl::UniqueFD fd_;
-  char buffer_[kChunkSize];
-  char* current_ = buffer_;
-  char* marker_ = buffer_;
-  char* end_ = buffer_ + kChunkSize;
+  const char* current_;
+  const char* marker_;
+  const char* end_;
   size_t number_bytes_read_ = 0;
   size_t number_records_read_ = 0;
 
diff --git a/garnet/bin/ktrace_provider/tags.cc b/garnet/bin/ktrace_provider/tags.cc
index 795b4439382..83462d1ac67 100644
--- a/garnet/bin/ktrace_provider/tags.cc
+++ b/garnet/bin/ktrace_provider/tags.cc
@@ -6,6 +6,8 @@
 
 #include <mutex>
 
+#include <lib/zircon-internal/ktrace.h>
+
 namespace ktrace_provider {
 namespace {
 
diff --git a/garnet/bin/ktrace_provider/tags.h b/garnet/bin/ktrace_provider/tags.h
index db93d18905c..b2b48b594d3 100644
--- a/garnet/bin/ktrace_provider/tags.h
+++ b/garnet/bin/ktrace_provider/tags.h
@@ -5,9 +5,7 @@
 #ifndef GARNET_BIN_KTRACE_PROVIDER_TAGS_H_
 #define GARNET_BIN_KTRACE_PROVIDER_TAGS_H_
 
-#include <lib/zircon-internal/ktrace.h>
-
-#include "stdint.h"
+#include <stdint.h>
 
 #include <unordered_map>
 
diff --git a/garnet/bin/ktrace_provider/test_reader.cc b/garnet/bin/ktrace_provider/test_reader.cc
new file mode 100644
index 00000000000..10f37b57a2f
--- /dev/null
+++ b/garnet/bin/ktrace_provider/test_reader.cc
@@ -0,0 +1,20 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "garnet/bin/ktrace_provider/test_reader.h"
+
+namespace ktrace_provider {
+
+TestReader::TestReader(const void* trace_data, size_t trace_data_size)
+  : Reader(reinterpret_cast<const char*>(trace_data), trace_data_size) {
+  // Mark the end of "read" data.
+  marker_ = end_;
+}
+
+void TestReader::ReadMoreData() {
+  // There is no more.
+  current_ = marker_;
+}
+
+}  // namespace ktrace_provider
diff --git a/garnet/bin/ktrace_provider/test_reader.h b/garnet/bin/ktrace_provider/test_reader.h
new file mode 100644
index 00000000000..fa29ed754b1
--- /dev/null
+++ b/garnet/bin/ktrace_provider/test_reader.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_KTRACE_PROVIDER_TEST_READER_H_
+#define GARNET_BIN_KTRACE_PROVIDER_TEST_READER_H_
+
+#include <src/lib/fxl/macros.h>
+
+#include "garnet/bin/ktrace_provider/reader.h"
+
+namespace ktrace_provider {
+
+class TestReader : public Reader {
+ public:
+  TestReader(const void* trace_data, size_t trace_data_size);
+
+ private:
+  void ReadMoreData() override;
+
+  FXL_DISALLOW_COPY_AND_ASSIGN(TestReader);
+};
+
+}  // namespace ktrace_provider
+
+#endif  // GARNET_BIN_KTRACE_PROVIDER_TEST_READER_H_
diff --git a/garnet/packages/tests/BUILD.gn b/garnet/packages/tests/BUILD.gn
index bc83c3a2c1d..e5a51acd05a 100644
--- a/garnet/packages/tests/BUILD.gn
+++ b/garnet/packages/tests/BUILD.gn
@@ -60,6 +60,7 @@ group("tracing") {
   testonly = true
   public_deps = [
     "//garnet/bin/cpuperf_provider:cpuperf_provider_tests",
+    "//garnet/bin/ktrace_provider:ktrace_provider_tests",
     "//garnet/bin/trace/tests:trace_tests",
     "//garnet/bin/trace2json:trace2json_tests($host_toolchain)",
     "//garnet/bin/trace_stress",
-- 
GitLab