diff --git a/garnet/bin/cpuperf_provider/BUILD.gn b/garnet/bin/cpuperf_provider/BUILD.gn
index eec859db08ca1455460df2f96dbebe1ef74e463e..0102a95d4ab720fb8040a983ba7eb6a651232002 100644
--- a/garnet/bin/cpuperf_provider/BUILD.gn
+++ b/garnet/bin/cpuperf_provider/BUILD.gn
@@ -103,10 +103,26 @@ executable("cpuperf_provider_tests_bin") {
   ]
 }
 
+executable("cpuperf_provider_integration_tests") {
+  testonly = true
+  output_name = "cpuperf_provider_integration_tests"
+
+  sources = [
+    "cpuperf_provider_integration_tests.cc"
+  ]
+
+  deps = [
+    "//garnet/lib/perfmon",
+    "//src/developer/tracing/lib/test_utils",
+    "//third_party/googletest:gtest",
+    "//zircon/public/lib/trace-reader",
+    "//zircon/public/lib/zx",
+  ]
+}
+
 test_package("cpuperf_provider_tests") {
   deps = [
     ":cpuperf_provider_tests_bin",
-    "//src/lib/fxl",
   ]
 
   tests = [
@@ -115,4 +131,17 @@ test_package("cpuperf_provider_tests") {
       environments = basic_envs
     },
   ]
+
+  if (current_cpu == "x64") {
+    deps += [
+      ":cpuperf_provider_integration_tests",
+    ]
+
+    tests += [
+      {
+        name = "cpuperf_provider_integration_tests"
+        environments = basic_envs
+      },
+    ]
+  }
 }
diff --git a/garnet/bin/cpuperf_provider/app.cc b/garnet/bin/cpuperf_provider/app.cc
index 99beeb0a51d8a17f6258f216e2b1bdf39749abde..7df2177e184dc95687db42f6d9e9e35d84b1e8a2 100644
--- a/garnet/bin/cpuperf_provider/app.cc
+++ b/garnet/bin/cpuperf_provider/app.cc
@@ -103,13 +103,11 @@ void App::PrintHelp() {
 
 void App::UpdateState() {
   if (trace_state() == TRACE_STARTED) {
+    FXL_DCHECK(!IsTracing());
     auto new_config = TraceConfig::Create(model_event_manager_.get(),
                                           trace_is_category_enabled);
-    if (new_config && trace_config_->Changed(*new_config)) {
-      StopTracing();
-      if (new_config->is_enabled()) {
-        StartTracing(std::move(new_config));
-      }
+    if (new_config != nullptr && new_config->is_enabled()) {
+      StartTracing(std::move(new_config));
     }
   } else {
     StopTracing();
@@ -157,8 +155,8 @@ Fail:
 }
 
 void App::StopTracing() {
-  if (!context_) {
-    return;  // not currently tracing
+  if (!IsTracing()) {
+    return;
   }
   FXL_DCHECK(trace_config_->is_enabled());
 
diff --git a/garnet/bin/cpuperf_provider/app.h b/garnet/bin/cpuperf_provider/app.h
index cd77d983f04b24bfaf25ba9a16d49b0be01cfab6..1a70dfe51aa0ddb3dde2fe7c9cdb7da2f75e1a6f 100644
--- a/garnet/bin/cpuperf_provider/app.h
+++ b/garnet/bin/cpuperf_provider/app.h
@@ -28,6 +28,7 @@ class App {
 
   void StartTracing(std::unique_ptr<TraceConfig> trace_config);
   void StopTracing();
+  bool IsTracing() const { return context_ != nullptr; }
 
   void PrintHelp();
 
diff --git a/garnet/bin/cpuperf_provider/cpuperf_provider_integration_tests.cc b/garnet/bin/cpuperf_provider/cpuperf_provider_integration_tests.cc
new file mode 100644
index 0000000000000000000000000000000000000000..16f88c344bbd89f1959ee0ed416db5a523ff910e
--- /dev/null
+++ b/garnet/bin/cpuperf_provider/cpuperf_provider_integration_tests.cc
@@ -0,0 +1,93 @@
+// 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 <gtest/gtest.h>
+#include <lib/zx/job.h>
+#include <lib/zx/process.h>
+#include <src/developer/tracing/lib/test_utils/spawn_and_wait.h>
+#include <src/lib/fxl/command_line.h>
+#include <src/lib/fxl/log_settings_command_line.h>
+#include <src/lib/fxl/logging.h>
+#include <trace-reader/file_reader.h>
+
+#include "garnet/lib/perfmon/controller.h"
+
+const char kTracePath[] = "/bin/trace";
+
+const char kDurationArg[] = "--duration=1";
+
+// Note: /data is no longer large enough in qemu sessions
+const char kOutputFile[] = "/tmp/test-trace.fxt";
+
+#if defined(__x86_64__)
+const char kCategoriesArg[] =
+    "--categories=cpu:fixed:instructions_retired,cpu:tally";
+const char kCategoryName[] = "cpu:perf";
+const char kTestEventName[] = "instructions_retired";
+#else
+#error "unsupported architecture"
+#endif
+
+TEST(CpuperfProvider, IntegrationTest) {
+  zx::job job;
+  ASSERT_EQ(zx::job::create(*zx::job::default_job(), 0, &job), ZX_OK);
+
+  zx::process child;
+  std::vector<std::string> argv{
+      kTracePath, "record", "--binary", kDurationArg, kCategoriesArg,
+      std::string("--output-file=") + kOutputFile};
+  ASSERT_EQ(SpawnProgram(job, argv, ZX_HANDLE_INVALID, &child), ZX_OK);
+
+  int return_code;
+  ASSERT_EQ(WaitAndGetExitCode(argv[0], child, &return_code), ZX_OK);
+  EXPECT_EQ(return_code, 0);
+
+  size_t record_count = 0;
+  size_t instructions_retired_count = 0;
+  auto record_consumer = [&record_count, &instructions_retired_count](trace::Record record) {
+    ++record_count;
+    if (record.type() == trace::RecordType::kEvent) {
+      const trace::Record::Event& event = record.GetEvent();
+      if (event.type() == trace::EventType::kCounter &&
+          event.category == kCategoryName &&
+          event.name == kTestEventName) {
+        ++instructions_retired_count;
+      }
+    }
+  };
+
+  bool got_error = false;
+  auto error_handler = [&got_error](fbl::String error) {
+    FXL_LOG(ERROR) << "While reading records got error: " << error.c_str();
+    got_error = true;
+  };
+
+  std::unique_ptr<trace::FileReader> reader;
+  ASSERT_TRUE(trace::FileReader::Create(kOutputFile,
+                                        std::move(record_consumer),
+                                        std::move(error_handler), &reader));
+  reader->ReadFile();
+  ASSERT_FALSE(got_error);
+
+  FXL_LOG(INFO) << "Got " << record_count << " records, "
+                << instructions_retired_count << " instructions";
+
+  ASSERT_GT(instructions_retired_count, 0u);
+}
+
+// Provide our own main so that --verbose,etc. are recognized.
+int main(int argc, char** argv) {
+  fxl::CommandLine cl = fxl::CommandLineFromArgcArgv(argc, argv);
+  if (!fxl::SetLogSettingsFromCommandLine(cl))
+    return EXIT_FAILURE;
+
+  if (!perfmon::Controller::IsSupported()) {
+    FXL_LOG(INFO) << "Exiting, perfmon device not supported";
+    return 0;
+  }
+
+  testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}
diff --git a/garnet/bin/cpuperf_provider/main.cc b/garnet/bin/cpuperf_provider/main.cc
index 2943db21bca1134bb88e90e6b07c029b42191339..c5652d8be241e26067e10bf2bc9de075f323a5c4 100644
--- a/garnet/bin/cpuperf_provider/main.cc
+++ b/garnet/bin/cpuperf_provider/main.cc
@@ -9,12 +9,18 @@
 #include <trace-provider/provider.h>
 
 #include "garnet/bin/cpuperf_provider/app.h"
+#include "garnet/lib/perfmon/controller.h"
 
 int main(int argc, const char** argv) {
   auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
   if (!fxl::SetLogSettingsFromCommandLine(command_line))
     return 1;
 
+  if (!perfmon::Controller::IsSupported()) {
+    FXL_LOG(INFO) << "Exiting, perfmon device not supported";
+    return 0;
+  }
+
   FXL_VLOG(2) << argv[0] << ": starting";
 
   async::Loop loop(&kAsyncLoopConfigAttachToThread);
diff --git a/garnet/bin/cpuperf_provider/meta/cpuperf_provider_integration_tests.cmx b/garnet/bin/cpuperf_provider/meta/cpuperf_provider_integration_tests.cmx
new file mode 100644
index 0000000000000000000000000000000000000000..d1e37b533075b9cb2fae4764384d48cf43154b45
--- /dev/null
+++ b/garnet/bin/cpuperf_provider/meta/cpuperf_provider_integration_tests.cmx
@@ -0,0 +1,33 @@
+{
+    "facets": {
+        "fuchsia.test": {
+            "injected-services": {
+                "fuchsia.tracelink.Registry": [
+                    "fuchsia-pkg://fuchsia.com/trace_manager#meta/trace_manager.cmx",
+                    "--verbose=4"
+                ],
+                "fuchsia.tracing.controller.Controller": [
+                    "fuchsia-pkg://fuchsia.com/trace_manager#meta/trace_manager.cmx",
+                    "--verbose=4"
+                ]
+            }
+        }
+    },
+    "program": {
+        "binary": "test/cpuperf_provider_integration_tests"
+    },
+    "sandbox": {
+        "dev": [ "sys/cpu-trace" ],
+        "features": [
+            "shell",
+            "system-temp"
+        ],
+        "services": [
+            "fuchsia.sys.Launcher",
+            "fuchsia.process.Launcher",
+            "fuchsia.process.Resolver",
+            "fuchsia.tracelink.Registry",
+            "fuchsia.tracing.controller.Controller"
+        ]
+    }
+}
diff --git a/garnet/packages/tests/BUILD.gn b/garnet/packages/tests/BUILD.gn
index a86339c8e9bbc31cc3bc79775b7e0486b04352d5..8bf2fe2ddd6d025768765f967b31046a951ed641 100644
--- a/garnet/packages/tests/BUILD.gn
+++ b/garnet/packages/tests/BUILD.gn
@@ -59,6 +59,7 @@ group("libinet") {
 group("tracing") {
   testonly = true
   public_deps = [
+    "//garnet/bin/cpuperf_provider:cpuperf_provider_tests",
     "//garnet/bin/trace/tests:trace_tests",
     "//garnet/bin/trace2json:trace2json_tests($host_toolchain)",
     "//garnet/bin/trace_stress",