You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by bm...@apache.org on 2015/09/03 00:02:20 UTC

mesos git commit: Add support for parsing different versions of perf sample output.

Repository: mesos
Updated Branches:
  refs/heads/master ca208f283 -> f4a40c1fe


Add support for parsing different versions of perf sample output.

Review: https://reviews.apache.org/r/37462


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/f4a40c1f
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/f4a40c1f
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/f4a40c1f

Branch: refs/heads/master
Commit: f4a40c1fef6fd489691264438b6b26737af07482
Parents: ca208f2
Author: Paul Brett <pa...@twopensource.com>
Authored: Wed Sep 2 14:03:43 2015 -0700
Committer: Benjamin Mahler <be...@gmail.com>
Committed: Wed Sep 2 14:49:22 2015 -0700

----------------------------------------------------------------------
 src/linux/perf.cpp                     | 136 ++++++++++++++++++----------
 src/linux/perf.hpp                     |   6 +-
 src/tests/containerizer/perf_tests.cpp |  12 ++-
 3 files changed, 97 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/f4a40c1f/src/linux/perf.cpp
----------------------------------------------------------------------
diff --git a/src/linux/perf.cpp b/src/linux/perf.cpp
index bb8b591..0011482 100644
--- a/src/linux/perf.cpp
+++ b/src/linux/perf.cpp
@@ -93,7 +93,7 @@ public:
 
   virtual ~Perf() {}
 
-  Future<string> future()
+  Future<string> output()
   {
     return promise.future();
   }
@@ -264,11 +264,11 @@ private:
 Future<Version> version()
 {
   internal::Perf* perf = new internal::Perf({"--version"});
-  Future<string> future = perf->future();
+  Future<string> output = perf->output();
   spawn(perf, true);
 
-  return future
-    .then([=](const string& output) -> Future<Version> {
+  return output
+    .then([](const string& output) -> Future<Version> {
       // Trim off the leading 'perf version ' text to convert.
       return Version::parse(strings::remove(
           output, "perf version ", strings::PREFIX));
@@ -276,15 +276,40 @@ Future<Version> version()
 };
 
 
+bool supported(const Version& version)
+{
+  // Require perf version >= 2.6.39 to support cgroups and formatting.
+  return version >= Version(2, 6, 39);
+}
+
+
+bool supported()
+{
+  Future<Version> version = perf::version();
+
+  // If perf does not respond in a reasonable time, mark as unsupported.
+  version.await(Seconds(5));
+
+  if (!version.isReady()) {
+    if (version.isFailed()) {
+      LOG(ERROR) << "Failed to get perf version: " << version.failure();
+    } else {
+      LOG(ERROR) << "Failed to get perf version: timeout of 5secs exceeded";
+    }
+
+    version.discard();
+    return false;
+  }
+
+  return supported(version.get());
+}
+
+
 Future<hashmap<string, mesos::PerfStatistics>> sample(
     const set<string>& events,
     const set<string>& cgroups,
     const Duration& duration)
 {
-  if (!supported()) {
-    return Failure("Perf is not supported");
-  }
-
   vector<string> argv = {
     "stat",
 
@@ -317,26 +342,38 @@ Future<hashmap<string, mesos::PerfStatistics>> sample(
   Time start = Clock::now();
 
   internal::Perf* perf = new internal::Perf(argv);
-  Future<string> future = perf->future();
+  Future<string> output = perf->output();
   spawn(perf, true);
 
-  auto parse = [start, duration](const string& output) ->
+  auto parse = [start, duration](
+      const tuple<Version, string> values) ->
       Future<hashmap<string, mesos::PerfStatistics>> {
-    Try<hashmap<string, mesos::PerfStatistics>> parse = perf::parse(output);
+    const Version& version = std::get<0>(values);
+    const string& output = std::get<1>(values);
+
+    // Check that the version is supported.
+    if (!supported(version)) {
+      return Failure("Perf " + stringify(version) + " is not supported");
+    }
+
+    Try<hashmap<string, mesos::PerfStatistics>> result =
+      perf::parse(output, version);
 
-    if (parse.isError()) {
-      return Failure("Failed to parse perf sample: " + parse.error());
+    if (result.isError()) {
+      return Failure("Failed to parse perf sample: " + result.error());
     }
 
-    foreachvalue (mesos::PerfStatistics& statistics, parse.get()) {
+    foreachvalue (mesos::PerfStatistics& statistics, result.get()) {
       statistics.set_timestamp(start.secs());
       statistics.set_duration(duration.secs());
     }
 
-    return parse.get();
+    return result.get();
   };
 
-  return future.then(parse);
+  // TODO(pbrett): Don't wait for these forever!
+  return process::collect(perf::version(), output)
+    .then(parse);
 }
 
 
@@ -355,29 +392,6 @@ bool valid(const set<string>& events)
 }
 
 
-bool supported()
-{
-  // Require perf version >= 2.6.39 to support cgroups and formatting.
-  Future<Version> version = perf::version();
-
-  // If perf does not respond in a reasonable time, mark as unsupported.
-  version.await(Seconds(5));
-
-  if (!version.isReady()) {
-    if (version.isFailed()) {
-      LOG(ERROR) << "Failed to get perf version: " << version.failure();
-    } else {
-      LOG(ERROR) << "Failed to get perf version: timeout of 5secs exceeded";
-    }
-
-    version.discard();
-    return false;
-  }
-
-  return version.get() >= Version(2, 6, 39);
-}
-
-
 struct Sample
 {
   const string value;
@@ -386,30 +400,51 @@ struct Sample
 
   // Convert a single line of perf output in CSV format (using
   // PERF_DELIMITER as a separator) to a sample.
-  static Try<Sample> parse(const string& line)
+  static Try<Sample> parse(const string& line, const Version& version)
   {
-    vector<string> tokens = strings::tokenize(line, PERF_DELIMITER);
-
-    // Expected format for an output line is 'value,event,cgroup'.
-    if (tokens.size() == 3) {
-      return Sample({tokens[0], internal::normalize(tokens[1]), tokens[2]});
+    // We use strings::split to separate the tokens
+    // because the unit field can be empty.
+    vector<string> tokens = strings::split(line, PERF_DELIMITER);
+
+    if (version >= Version(4, 0, 0)) {
+      // Optional running time and ratio were introduced in Linux v4.0,
+      // which make the format either:
+      //   value,unit,event,cgroup
+      //   value,unit,event,cgroup,running,ratio
+      if ((tokens.size() == 4) || (tokens.size() == 6)) {
+        return Sample({tokens[0], internal::normalize(tokens[2]), tokens[3]});
+      }
+    } else if (version >= Version(3, 13, 0)) {
+      // Unit was added in Linux v3.13, making the format:
+      //   value,unit,event,cgroup
+      if (tokens.size() == 4) {
+        return Sample({tokens[0], internal::normalize(tokens[2]), tokens[3]});
+      }
     } else {
-      return Error("Unexpected number of fields");
+      // Expected format for Linux kernel <= 3.12 is:
+      //   value,event,cgroup
+      if (tokens.size() == 3) {
+        return Sample({tokens[0], internal::normalize(tokens[1]), tokens[2]});
+      }
     }
+
+    return Error("Unexpected number of fields");
   }
 };
 
 
-Try<hashmap<string, mesos::PerfStatistics>> parse(const string& output)
+Try<hashmap<string, mesos::PerfStatistics>> parse(
+    const string& output,
+    const Version& version)
 {
   hashmap<string, mesos::PerfStatistics> statistics;
 
   foreach (const string& line, strings::tokenize(output, "\n")) {
-    Try<Sample> sample = Sample::parse(line);
+    Try<Sample> sample = Sample::parse(line, version);
 
     if (sample.isError()) {
       return Error("Failed to parse perf sample line '" + line + "': " +
-                   sample.error() );
+                   sample.error());
     }
 
     if (!statistics.contains(sample->cgroup)) {
@@ -423,7 +458,8 @@ Try<hashmap<string, mesos::PerfStatistics>> parse(const string& output)
           sample->event);
 
     if (field == NULL) {
-      return Error("Unexpected perf output at line: " + line);
+      return Error("Unexpected event '" + sample->event + "'"
+                   " in perf output at line: " + line);
     }
 
     if (sample->value == "<not supported>") {

http://git-wip-us.apache.org/repos/asf/mesos/blob/f4a40c1f/src/linux/perf.hpp
----------------------------------------------------------------------
diff --git a/src/linux/perf.hpp b/src/linux/perf.hpp
index c444456..d10968c 100644
--- a/src/linux/perf.hpp
+++ b/src/linux/perf.hpp
@@ -53,9 +53,11 @@ bool valid(const std::set<std::string>& events);
 bool supported();
 
 
-// Note: Exposed for testing purposes.
+// Note: The parse function is exposed to allow testing of the
+// multiple supported perf stat output formats.
 Try<hashmap<std::string, mesos::PerfStatistics>> parse(
-    const std::string& output);
+    const std::string& output,
+    const Version& version);
 
 } // namespace perf {
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/f4a40c1f/src/tests/containerizer/perf_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/perf_tests.cpp b/src/tests/containerizer/perf_tests.cpp
index 8c29ca3..bef475e 100644
--- a/src/tests/containerizer/perf_tests.cpp
+++ b/src/tests/containerizer/perf_tests.cpp
@@ -64,7 +64,8 @@ TEST_F(PerfTest, Parse)
     perf::parse("123,cycles,cgroup1\n"
                 "456,cycles,cgroup2\n"
                 "0.456,task-clock,cgroup2\n"
-                "0.123,task-clock,cgroup1");
+                "0.123,task-clock,cgroup1",
+                Version(3, 12, 0));
 
   ASSERT_SOME(parse);
   EXPECT_EQ(2u, parse->size());
@@ -86,7 +87,7 @@ TEST_F(PerfTest, Parse)
   EXPECT_EQ(0.456, statistics.task_clock());
 
   // Statistics reporting <not supported> should not appear.
-  parse = perf::parse("<not supported>,cycles,cgroup1");
+  parse = perf::parse("<not supported>,cycles,cgroup1", Version(3, 12, 0));
   ASSERT_SOME(parse);
 
   ASSERT_TRUE(parse->contains("cgroup1"));
@@ -95,7 +96,8 @@ TEST_F(PerfTest, Parse)
 
   // Statistics reporting <not counted> should be zero.
   parse = perf::parse("<not counted>,cycles,cgroup1\n"
-                      "<not counted>,task-clock,cgroup1");
+                      "<not counted>,task-clock,cgroup1",
+                      Version(3, 12, 0));
   ASSERT_SOME(parse);
 
   ASSERT_TRUE(parse->contains("cgroup1"));
@@ -107,10 +109,10 @@ TEST_F(PerfTest, Parse)
   EXPECT_EQ(0.0, statistics.task_clock());
 
   // Check parsing fails.
-  parse = perf::parse("1,cycles\ngarbage");
+  parse = perf::parse("1,cycles\ngarbage", Version(3, 12, 0));
   EXPECT_ERROR(parse);
 
-  parse = perf::parse("1,unknown-field");
+  parse = perf::parse("1,unknown-field", Version(3, 12, 0));
   EXPECT_ERROR(parse);
 }