Refactored PerfSampler into a more general Perf class for re-use.

Review: https://reviews.apache.org/r/37423
diff --git a/src/linux/perf.cpp b/src/linux/perf.cpp
index cb1a13d..cab6271 100644
--- a/src/linux/perf.cpp
+++ b/src/linux/perf.cpp
@@ -73,7 +73,7 @@
     const Duration& duration)
 {
   vector<string> argv = {
-    "perf", "stat",
+    "stat",
 
     // System-wide collection from all CPUs.
     "--all-cpus",
@@ -123,7 +123,7 @@
     const Duration& duration)
 {
   vector<string> argv = {
-    "perf", "stat",
+    "stat",
 
     // System-wide collection from all CPUs.
     "--all-cpus",
@@ -155,15 +155,28 @@
 }
 
 
-class PerfSampler : public Process<PerfSampler>
+// Executes the 'perf' command using the supplied arguments, and
+// returns stdout as the value of the future or a failure if calling
+// the command fails or the command returns a non-zero exit code.
+//
+// TODO(bmahler): Add a process::os::shell to generalize this.
+class Perf : public Process<Perf>
 {
 public:
-  PerfSampler(const vector<string>& _argv, const Duration& _duration)
-    : argv(_argv), duration(_duration) {}
+  Perf(const vector<string>& _argv) : argv(_argv)
+  {
+    // The first argument should be 'perf'. Note that this is
+    // a bit hacky because this class is specialized to only
+    // execute the 'perf' binary. Ultimately, this should be
+    // generalized to something like process::os::shell.
+    if (argv.empty() || argv.front() != "perf") {
+      argv.insert(argv.begin(), "perf");
+    }
+  }
 
-  virtual ~PerfSampler() {}
+  virtual ~Perf() {}
 
-  Future<hashmap<string, mesos::PerfStatistics>> future()
+  Future<string> future()
   {
     return promise.future();
   }
@@ -173,18 +186,9 @@
   {
     // Stop when no one cares.
     promise.future().onDiscard(lambda::bind(
-          static_cast<void(*)(const UPID&, bool)>(terminate), self(), true));
+        static_cast<void(*)(const UPID&, bool)>(terminate), self(), true));
 
-    if (duration < Seconds(0)) {
-      promise.fail("Perf sample duration cannot be negative: '" +
-                   stringify(duration.secs()) + "'");
-      terminate(self());
-      return;
-    }
-
-    start = Clock::now();
-
-    sample();
+    execute();
   }
 
   virtual void finalize()
@@ -273,7 +277,7 @@
     }
   }
 
-  void sample()
+  void execute()
   {
     Try<Subprocess> _perf = subprocess(
         "perf",
@@ -311,7 +315,7 @@
         } else if (status.get().isNone()) {
           error = Error("Failed to execute perf: failed to reap");
         } else if (status.get().get() != 0) {
-          error = Error("Failed to collect perf statistics: " +
+          error = Error("Failed to execute perf: " +
                         WSTRINGIFY(status.get().get()));
         } else if (!output.isReady()) {
           error = Error("Failed to read perf output: " +
@@ -324,35 +328,15 @@
           return;
         }
 
-        // Parse output from stdout.
-        Try<hashmap<string, mesos::PerfStatistics>> parse =
-            perf::parse(output.get());
-
-        if (parse.isError()) {
-          promise.fail("Failed to parse perf output: " + parse.error());
-          terminate(self());
-          return;
-        }
-
-        // Create a non-const copy from the Try<> so we can set the
-        // timestamp and duration.
-        hashmap<string, mesos::PerfStatistics> statistics = parse.get();
-        foreachvalue (mesos::PerfStatistics& s, statistics) {
-          s.set_timestamp(start.secs());
-          s.set_duration(duration.secs());
-        }
-
-        promise.set(statistics);
+        promise.set(output.get());
         terminate(self());
         return;
     }));
-}
+  }
 
-  const vector<string> argv;
-  const Duration duration;
-  Time start;
+  vector<string> argv;
+  Promise<string> promise;
   Option<Subprocess> perf;
-  Promise<hashmap<string, mesos::PerfStatistics>> promise;
 };
 
 
@@ -364,6 +348,36 @@
   return statistics.get(key).get();
 }
 
+
+Future<hashmap<string, mesos::PerfStatistics>> sample(
+    const vector<string>& argv,
+    const Duration& duration)
+{
+  Time start = Clock::now();
+
+  Perf* perf = new Perf(argv);
+  Future<string> future = perf->future();
+  spawn(perf, true);
+
+  auto parse = [start, duration](const string& output) ->
+      Future<hashmap<string, mesos::PerfStatistics>> {
+    Try<hashmap<string, mesos::PerfStatistics>> parse = perf::parse(output);
+
+    if (parse.isError()) {
+      return Failure("Failed to parse perf sample: " + parse.error());
+    }
+
+    foreachvalue (mesos::PerfStatistics& statistics, parse.get()) {
+      statistics.set_timestamp(start.secs());
+      statistics.set_duration(duration.secs());
+    }
+
+    return parse.get();
+  };
+
+  return future.then(parse);
+}
+
 } // namespace internal {
 
 
@@ -387,11 +401,7 @@
     return Failure("Perf is not supported");
   }
 
-  const vector<string> argv = internal::argv(events, pids, duration);
-  internal::PerfSampler* sampler = new internal::PerfSampler(argv, duration);
-  Future<hashmap<string, mesos::PerfStatistics>> future = sampler->future();
-  spawn(sampler, true);
-  return future
+  return internal::sample(internal::argv(events, pids, duration), duration)
     .then(lambda::bind(&internal::select, PIDS_KEY, lambda::_1));
 }
 
@@ -417,11 +427,7 @@
     return Failure("Perf is not supported");
   }
 
-  const vector<string> argv = internal::argv(events, cgroups, duration);
-  internal::PerfSampler* sampler = new internal::PerfSampler(argv, duration);
-  Future<hashmap<string, mesos::PerfStatistics>> future = sampler->future();
-  spawn(sampler, true);
-  return future;
+  return internal::sample(internal::argv(events, cgroups, duration), duration);
 }