[metrics] Report some ART metadata
Adds reporting for some metadata associated with ART metrics,
including timestamp relative to ART startup, the session id, and the
process's user id. It additionally outputs placeholders for the
compilation reason and compiler filter, but these need some additional
plumbing from the Runtime and OatFileManager to fill in, so those will
come in a followup CL.
This CL also includes a fair amount of refactoring around metrics
reporting and handling the session data.
Example output:
*** ART internal metrics ***
Metadata:
timestamp_since_start_ms: 768
session_id: 5026277321588503825
uid: 123456
compilation_reason: Unknown
compiler_filter: (unspecified)
Metrics:
ClassVerificationTotalTime: count = 4167
JitMethodCompileTime: range = 0...1000000, buckets: 7,0,0,0,0,0
*** Done dumping ART internal metrics ***
Test: ./test/run-test --host --jit 2232-write-metrics-to-log
Change-Id: Ic74b503b135d71099d9e26bf660b60e4cc3a46bc
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index 60dcf17..c3a6364 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -24,6 +24,7 @@
"base/arena_allocator.cc",
"base/arena_bit_vector.cc",
"base/bit_vector.cc",
+ "base/compiler_filter.cc",
"base/enums.cc",
"base/file_magic.cc",
"base/file_utils.cc",
@@ -243,6 +244,7 @@
"base/bit_table_test.cc",
"base/bit_utils_test.cc",
"base/bit_vector_test.cc",
+ "base/compiler_filter_test.cc",
"base/file_utils_test.cc",
"base/hash_map_test.cc",
"base/hash_set_test.cc",
diff --git a/libartbase/base/compiler_filter.cc b/libartbase/base/compiler_filter.cc
new file mode 100644
index 0000000..6291329
--- /dev/null
+++ b/libartbase/base/compiler_filter.cc
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compiler_filter.h"
+
+#include <ostream>
+
+#include "base/utils.h"
+
+namespace art {
+
+bool CompilerFilter::IsAotCompilationEnabled(Filter filter) {
+ switch (filter) {
+ case CompilerFilter::kAssumeVerified:
+ case CompilerFilter::kExtract:
+ case CompilerFilter::kVerify: return false;
+
+ case CompilerFilter::kSpaceProfile:
+ case CompilerFilter::kSpace:
+ case CompilerFilter::kSpeedProfile:
+ case CompilerFilter::kSpeed:
+ case CompilerFilter::kEverythingProfile:
+ case CompilerFilter::kEverything: return true;
+ }
+ UNREACHABLE();
+}
+
+bool CompilerFilter::IsJniCompilationEnabled(Filter filter) {
+ switch (filter) {
+ case CompilerFilter::kAssumeVerified:
+ case CompilerFilter::kExtract:
+ case CompilerFilter::kVerify: return false;
+
+ case CompilerFilter::kSpaceProfile:
+ case CompilerFilter::kSpace:
+ case CompilerFilter::kSpeedProfile:
+ case CompilerFilter::kSpeed:
+ case CompilerFilter::kEverythingProfile:
+ case CompilerFilter::kEverything: return true;
+ }
+ UNREACHABLE();
+}
+
+bool CompilerFilter::IsQuickeningCompilationEnabled(Filter filter ATTRIBUTE_UNUSED) {
+ return false;
+}
+
+bool CompilerFilter::IsAnyCompilationEnabled(Filter filter) {
+ return IsJniCompilationEnabled(filter) ||
+ IsQuickeningCompilationEnabled(filter) ||
+ IsAotCompilationEnabled(filter);
+}
+
+bool CompilerFilter::IsVerificationEnabled(Filter filter) {
+ switch (filter) {
+ case CompilerFilter::kAssumeVerified:
+ case CompilerFilter::kExtract: return false;
+
+ case CompilerFilter::kVerify:
+ case CompilerFilter::kSpaceProfile:
+ case CompilerFilter::kSpace:
+ case CompilerFilter::kSpeedProfile:
+ case CompilerFilter::kSpeed:
+ case CompilerFilter::kEverythingProfile:
+ case CompilerFilter::kEverything: return true;
+ }
+ UNREACHABLE();
+}
+
+bool CompilerFilter::DependsOnImageChecksum(Filter filter) {
+ // We run dex2dex with verification, so the oat file will depend on the
+ // image checksum if verification is enabled.
+ return IsVerificationEnabled(filter);
+}
+
+bool CompilerFilter::DependsOnProfile(Filter filter) {
+ switch (filter) {
+ case CompilerFilter::kAssumeVerified:
+ case CompilerFilter::kExtract:
+ case CompilerFilter::kVerify:
+ case CompilerFilter::kSpace:
+ case CompilerFilter::kSpeed:
+ case CompilerFilter::kEverything: return false;
+
+ case CompilerFilter::kSpaceProfile:
+ case CompilerFilter::kSpeedProfile:
+ case CompilerFilter::kEverythingProfile: return true;
+ }
+ UNREACHABLE();
+}
+
+CompilerFilter::Filter CompilerFilter::GetNonProfileDependentFilterFrom(Filter filter) {
+ switch (filter) {
+ case CompilerFilter::kAssumeVerified:
+ case CompilerFilter::kExtract:
+ case CompilerFilter::kVerify:
+ case CompilerFilter::kSpace:
+ case CompilerFilter::kSpeed:
+ case CompilerFilter::kEverything:
+ return filter;
+
+ case CompilerFilter::kSpaceProfile:
+ return CompilerFilter::kSpace;
+
+ case CompilerFilter::kSpeedProfile:
+ return CompilerFilter::kSpeed;
+
+ case CompilerFilter::kEverythingProfile:
+ return CompilerFilter::kEverything;
+ }
+ UNREACHABLE();
+}
+
+CompilerFilter::Filter CompilerFilter::GetSafeModeFilterFrom(Filter filter) {
+ // For safe mode, we should not return a filter that generates AOT compiled
+ // code.
+ switch (filter) {
+ case CompilerFilter::kAssumeVerified:
+ case CompilerFilter::kExtract:
+ case CompilerFilter::kVerify:
+ return filter;
+
+ case CompilerFilter::kSpace:
+ case CompilerFilter::kSpeed:
+ case CompilerFilter::kEverything:
+ case CompilerFilter::kSpaceProfile:
+ case CompilerFilter::kSpeedProfile:
+ case CompilerFilter::kEverythingProfile:
+ return CompilerFilter::kVerify;
+ }
+ UNREACHABLE();
+}
+
+bool CompilerFilter::IsAsGoodAs(Filter current, Filter target) {
+ return current >= target;
+}
+
+bool CompilerFilter::IsBetter(Filter current, Filter target) {
+ return current > target;
+}
+
+std::string CompilerFilter::NameOfFilter(Filter filter) {
+ switch (filter) {
+ case CompilerFilter::kAssumeVerified: return "assume-verified";
+ case CompilerFilter::kExtract: return "extract";
+ case CompilerFilter::kVerify: return "verify";
+ case CompilerFilter::kSpaceProfile: return "space-profile";
+ case CompilerFilter::kSpace: return "space";
+ case CompilerFilter::kSpeedProfile: return "speed-profile";
+ case CompilerFilter::kSpeed: return "speed";
+ case CompilerFilter::kEverythingProfile: return "everything-profile";
+ case CompilerFilter::kEverything: return "everything";
+ }
+ UNREACHABLE();
+}
+
+bool CompilerFilter::ParseCompilerFilter(const char* option, Filter* filter) {
+ CHECK(filter != nullptr);
+
+ if (strcmp(option, "verify-none") == 0) {
+ LOG(WARNING) << "'verify-none' is an obsolete compiler filter name that will be "
+ << "removed in future releases, please use 'assume-verified' instead.";
+ *filter = kAssumeVerified;
+ } else if (strcmp(option, "interpret-only") == 0) {
+ LOG(WARNING) << "'interpret-only' is an obsolete compiler filter name that will be "
+ << "removed in future releases, please use 'quicken' instead.";
+ *filter = kVerify;
+ } else if (strcmp(option, "verify-profile") == 0) {
+ LOG(WARNING) << "'verify-profile' is an obsolete compiler filter name that will be "
+ << "removed in future releases, please use 'verify' instead.";
+ *filter = kVerify;
+ } else if (strcmp(option, "verify-at-runtime") == 0) {
+ LOG(WARNING) << "'verify-at-runtime' is an obsolete compiler filter name that will be "
+ << "removed in future releases, please use 'extract' instead.";
+ *filter = kExtract;
+ } else if (strcmp(option, "balanced") == 0) {
+ LOG(WARNING) << "'balanced' is an obsolete compiler filter name that will be "
+ << "removed in future releases, please use 'speed' instead.";
+ *filter = kSpeed;
+ } else if (strcmp(option, "time") == 0) {
+ LOG(WARNING) << "'time' is an obsolete compiler filter name that will be "
+ << "removed in future releases, please use 'space' instead.";
+ *filter = kSpace;
+ } else if (strcmp(option, "assume-verified") == 0) {
+ *filter = kAssumeVerified;
+ } else if (strcmp(option, "extract") == 0) {
+ *filter = kExtract;
+ } else if (strcmp(option, "verify") == 0) {
+ *filter = kVerify;
+ } else if (strcmp(option, "quicken") == 0) {
+ // b/170086509 'quicken' becomes an alias to 'verify.
+ *filter = kVerify;
+ } else if (strcmp(option, "space") == 0) {
+ *filter = kSpace;
+ } else if (strcmp(option, "space-profile") == 0) {
+ *filter = kSpaceProfile;
+ } else if (strcmp(option, "speed") == 0) {
+ *filter = kSpeed;
+ } else if (strcmp(option, "speed-profile") == 0) {
+ *filter = kSpeedProfile;
+ } else if (strcmp(option, "everything") == 0) {
+ *filter = kEverything;
+ } else if (strcmp(option, "everything-profile") == 0) {
+ *filter = kEverythingProfile;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+const char* CompilerFilter::DescribeOptions() {
+ return "assume-verified|extract|verify|quicken|space{,-profile}|speed{,-profile}|"
+ "everything{,-profile}";
+}
+
+std::ostream& operator<<(std::ostream& os, const CompilerFilter::Filter& rhs) {
+ return os << CompilerFilter::NameOfFilter(rhs);
+}
+
+} // namespace art
diff --git a/libartbase/base/compiler_filter.h b/libartbase/base/compiler_filter.h
new file mode 100644
index 0000000..b4a83ec
--- /dev/null
+++ b/libartbase/base/compiler_filter.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_LIBARTBASE_BASE_COMPILER_FILTER_H_
+#define ART_LIBARTBASE_BASE_COMPILER_FILTER_H_
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+namespace art {
+
+class CompilerFilter final {
+ public:
+ // Note: Order here matters. Later filter choices are considered "as good
+ // as" earlier filter choices.
+ enum Filter {
+ kAssumeVerified, // Skip verification but mark all classes as verified anyway.
+ kExtract, // Delay verication to runtime, do not compile anything.
+ kVerify, // Only verify classes.
+ kSpaceProfile, // Maximize space savings based on profile.
+ kSpace, // Maximize space savings.
+ kSpeedProfile, // Maximize runtime performance based on profile.
+ kSpeed, // Maximize runtime performance.
+ kEverythingProfile, // Compile everything capable of being compiled based on profile.
+ kEverything, // Compile everything capable of being compiled.
+ };
+
+ static const Filter kDefaultCompilerFilter = kSpeed;
+
+ // Returns true if an oat file with this compiler filter contains
+ // compiled executable code for bytecode.
+ static bool IsAotCompilationEnabled(Filter filter);
+
+ // Returns true if an oat file with this compiler filter contains
+ // compiled executable code for bytecode, JNI methods, or quickened dex
+ // bytecode.
+ static bool IsAnyCompilationEnabled(Filter filter);
+
+ // Returns true if an oat file with this compiler filter contains
+ // compiled executable code for JNI methods.
+ static bool IsJniCompilationEnabled(Filter filter);
+
+ // Returns true if an oat file with this compiler filter contains
+ // quickened dex bytecode.
+ static bool IsQuickeningCompilationEnabled(Filter filter);
+
+ // Returns true if this compiler filter requires running verification.
+ static bool IsVerificationEnabled(Filter filter);
+
+ // Returns true if an oat file with this compiler filter depends on the
+ // boot image checksum.
+ static bool DependsOnImageChecksum(Filter filter);
+
+ // Returns true if an oat file with this compiler filter depends on a
+ // profile.
+ static bool DependsOnProfile(Filter filter);
+
+ // Returns a non-profile-guided version of the given filter.
+ static Filter GetNonProfileDependentFilterFrom(Filter filter);
+
+ // Returns a filter suitable for safe mode.
+ static Filter GetSafeModeFilterFrom(Filter filter);
+
+ // Returns true if the 'current' compiler filter is considered at least as
+ // good as the 'target' compilation type.
+ // For example: kSpeed is as good as kInterpretOnly, but kInterpretOnly is
+ // not as good as kSpeed.
+ static bool IsAsGoodAs(Filter current, Filter target);
+
+ // Returns true if 'current' compiler filter is better than 'target' compiler
+ // filter. Compared to IsAsGoodAs, this returns false if the compiler filters are
+ // equal.
+ static bool IsBetter(Filter current, Filter target);
+
+ // Return the flag name of the given filter.
+ // For example: given kVerifyAtRuntime, returns "verify-at-runtime".
+ // The name returned corresponds to the name accepted by
+ // ParseCompilerFilter.
+ static std::string NameOfFilter(Filter filter);
+
+ // Parse the compiler filter from the given name.
+ // Returns true and sets filter to the parsed value if name refers to a
+ // valid filter. Returns false if no filter matches that name.
+ // 'filter' must be non-null.
+ static bool ParseCompilerFilter(const char* name, /*out*/Filter* filter);
+
+ static const char* DescribeOptions();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CompilerFilter);
+};
+
+std::ostream& operator<<(std::ostream& os, const CompilerFilter::Filter& rhs);
+
+} // namespace art
+
+#endif // ART_LIBARTBASE_BASE_COMPILER_FILTER_H_
diff --git a/libartbase/base/compiler_filter_test.cc b/libartbase/base/compiler_filter_test.cc
new file mode 100644
index 0000000..df7c8e7
--- /dev/null
+++ b/libartbase/base/compiler_filter_test.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compiler_filter.h"
+
+#include <gtest/gtest.h>
+
+namespace art {
+
+static void TestCompilerFilterName(CompilerFilter::Filter filter, const std::string& name) {
+ CompilerFilter::Filter parsed;
+ EXPECT_TRUE(CompilerFilter::ParseCompilerFilter(name.c_str(), &parsed));
+ EXPECT_EQ(filter, parsed);
+
+ EXPECT_EQ(name, CompilerFilter::NameOfFilter(filter));
+}
+
+static void TestSafeModeFilter(CompilerFilter::Filter expected, const std::string& name) {
+ CompilerFilter::Filter parsed;
+ EXPECT_TRUE(CompilerFilter::ParseCompilerFilter(name.c_str(), &parsed));
+ EXPECT_EQ(expected, CompilerFilter::GetSafeModeFilterFrom(parsed));
+}
+
+
+// Verify the dexopt status values from dalvik.system.DexFile
+// match the OatFileAssistant::DexOptStatus values.
+TEST(CompilerFilterTest, ParseCompilerFilter) {
+ CompilerFilter::Filter filter;
+
+ TestCompilerFilterName(CompilerFilter::kAssumeVerified, "assume-verified");
+ TestCompilerFilterName(CompilerFilter::kExtract, "extract");
+ TestCompilerFilterName(CompilerFilter::kVerify, "verify");
+ TestCompilerFilterName(CompilerFilter::kSpaceProfile, "space-profile");
+ TestCompilerFilterName(CompilerFilter::kSpace, "space");
+ TestCompilerFilterName(CompilerFilter::kSpeedProfile, "speed-profile");
+ TestCompilerFilterName(CompilerFilter::kSpeed, "speed");
+ TestCompilerFilterName(CompilerFilter::kEverythingProfile, "everything-profile");
+ TestCompilerFilterName(CompilerFilter::kEverything, "everything");
+
+ EXPECT_FALSE(CompilerFilter::ParseCompilerFilter("super-awesome-filter", &filter));
+}
+
+TEST(CompilerFilterTest, SafeModeFilter) {
+ TestSafeModeFilter(CompilerFilter::kAssumeVerified, "assume-verified");
+ TestSafeModeFilter(CompilerFilter::kExtract, "extract");
+ TestSafeModeFilter(CompilerFilter::kVerify, "verify");
+ TestSafeModeFilter(CompilerFilter::kVerify, "space-profile");
+ TestSafeModeFilter(CompilerFilter::kVerify, "space");
+ TestSafeModeFilter(CompilerFilter::kVerify, "speed-profile");
+ TestSafeModeFilter(CompilerFilter::kVerify, "speed");
+ TestSafeModeFilter(CompilerFilter::kVerify, "everything-profile");
+ TestSafeModeFilter(CompilerFilter::kVerify, "everything");
+}
+
+} // namespace art
diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h
index 18f84e9..20bbc52 100644
--- a/libartbase/base/metrics/metrics.h
+++ b/libartbase/base/metrics/metrics.h
@@ -22,12 +22,13 @@
#include <array>
#include <atomic>
#include <optional>
-#include <ostream>
+#include <sstream>
#include <string_view>
#include <thread>
#include <vector>
#include "android-base/logging.h"
+#include "base/compiler_filter.h"
#include "base/time_utils.h"
#pragma clang diagnostic push
@@ -79,10 +80,59 @@
#undef ART_HISTOGRAM
};
+// We log compilation reasons as part of the metadata we report. Since elsewhere compilation reasons
+// are specified as a string, we define them as an enum here which indicates the reasons that we
+// support.
+enum class CompilationReason {
+ kError,
+ kUnknown,
+ kFirstBoot,
+ kBoot,
+ kInstall,
+ kBgDexopt,
+ kABOTA,
+ kInactive,
+ kShared,
+ kInstallWithDexMetadata,
+};
+
+constexpr const char* CompilationReasonName(CompilationReason reason) {
+ switch (reason) {
+ case CompilationReason::kError:
+ return "Error";
+ case CompilationReason::kUnknown:
+ return "Unknown";
+ case CompilationReason::kFirstBoot:
+ return "FirstBoot";
+ case CompilationReason::kBoot:
+ return "Boot";
+ case CompilationReason::kInstall:
+ return "Install";
+ case CompilationReason::kBgDexopt:
+ return "BgDexopt";
+ case CompilationReason::kABOTA:
+ return "ABOTA";
+ case CompilationReason::kInactive:
+ return "Inactive";
+ case CompilationReason::kShared:
+ return "Shared";
+ case CompilationReason::kInstallWithDexMetadata:
+ return "InstallWithDexMetadata";
+ }
+}
+
+// SessionData contains metadata about a metrics session (basically the lifetime of an ART process).
+// This information should not change for the lifetime of the session.
struct SessionData {
- const uint64_t session_id;
- const std::string_view package_name;
- // TODO: compiler filter / dexopt state
+ static SessionData CreateDefault();
+
+ static constexpr int64_t kInvalidSessionId = -1;
+ static constexpr int32_t kInvalidUserId = -1;
+
+ int64_t session_id;
+ int32_t uid;
+ CompilationReason compilation_reason;
+ std::optional<CompilerFilter::Filter> compiler_filter;
};
// MetricsBackends are used by a metrics reporter to write metrics to some external location. For
@@ -91,7 +141,6 @@
public:
virtual ~MetricsBackend() {}
- protected:
// Begins an ART metrics session.
//
// This is called by the metrics reporter when the runtime is starting up. The session_data
@@ -100,12 +149,9 @@
// for this process.
virtual void BeginSession(const SessionData& session_data) = 0;
- // Marks the end of a metrics session.
- //
- // The metrics reporter will call this when metrics reported ends (e.g. when the runtime is
- // shutting down). No further metrics will be reported for this session. Note that EndSession is
- // not guaranteed to be called, since clean shutdowns for the runtime are quite rare in practice.
- virtual void EndSession() = 0;
+ protected:
+ // Called by the metrics reporter to indicate that a new metrics report is starting.
+ virtual void BeginReport(uint64_t timestamp_millis) = 0;
// Called by the metrics reporter to give the current value of the counter with id counter_type.
//
@@ -129,10 +175,14 @@
int64_t maximum_value,
const std::vector<uint32_t>& buckets) = 0;
+ // Called by the metrics reporter to indicate that the current metrics report is complete.
+ virtual void EndReport() = 0;
+
template <DatumId counter_type>
friend class MetricsCounter;
template <DatumId histogram_type, size_t num_buckets, int64_t low_value, int64_t high_value>
friend class MetricsHistogram;
+ friend class ArtMetrics;
};
template <DatumId counter_type>
@@ -208,13 +258,16 @@
static_assert(std::atomic<value_t>::is_always_lock_free);
};
-// A backend that writes metrics in a human-readable format to an std::ostream.
-class StreamBackend : public MetricsBackend {
+// A backend that writes metrics in a human-readable format to a string.
+//
+// This is used as a base for LogBackend and FileBackend.
+class StringBackend : public MetricsBackend {
public:
- explicit StreamBackend(std::ostream& os);
+ StringBackend();
void BeginSession(const SessionData& session_data) override;
- void EndSession() override;
+
+ void BeginReport(uint64_t timestamp_millis) override;
void ReportCounter(DatumId counter_type, uint64_t value) override;
@@ -223,8 +276,40 @@
int64_t high_value,
const std::vector<uint32_t>& buckets) override;
+ void EndReport() override;
+
+ std::string GetAndResetBuffer();
+
private:
- std::ostream& os_;
+ std::ostringstream os_;
+ std::optional<SessionData> session_data_;
+};
+
+// A backend that writes metrics in human-readable format to the log (i.e. logcat).
+class LogBackend : public StringBackend {
+ public:
+ explicit LogBackend(android::base::LogSeverity level);
+
+ void BeginReport(uint64_t timestamp_millis) override;
+ void EndReport() override;
+
+ private:
+ android::base::LogSeverity level_;
+};
+
+// A backend that writes metrics to a file.
+//
+// These are currently written in the same human-readable format used by StringBackend and
+// LogBackend, but we will probably want a more machine-readable format in the future.
+class FileBackend : public StringBackend {
+ public:
+ explicit FileBackend(std::string filename);
+
+ void BeginReport(uint64_t timestamp_millis) override;
+ void EndReport() override;
+
+ private:
+ std::string filename_;
};
/**
@@ -323,14 +408,7 @@
#undef ART_HISTOGRAM
private:
- // This field is only included to allow us expand the ART_COUNTERS and ART_HISTOGRAMS macro in
- // the initializer list in ArtMetrics::ArtMetrics. See metrics.cc for how it's used.
- //
- // It's declared as a zero-length array so it has no runtime space impact.
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-private-field"
- int unused_[0];
-#pragma clang diagnostic pop // -Wunused-private-field
+ uint64_t beginning_timestamp_;
#define ART_COUNTER(name) MetricsCounter<DatumId::k##name> name##_;
ART_COUNTERS(ART_COUNTER)
diff --git a/libartbase/base/metrics/metrics_common.cc b/libartbase/base/metrics/metrics_common.cc
index 81d7215..c650fdf 100644
--- a/libartbase/base/metrics/metrics_common.cc
+++ b/libartbase/base/metrics/metrics_common.cc
@@ -16,8 +16,10 @@
#include <sstream>
+#include "android-base/file.h"
#include "android-base/logging.h"
#include "base/macros.h"
+#include "base/scoped_flock.h"
#include "metrics.h"
#pragma clang diagnostic push
@@ -46,7 +48,22 @@
}
}
-ArtMetrics::ArtMetrics() : unused_ {}
+SessionData SessionData::CreateDefault() {
+#ifdef _WIN32
+ int32_t uid = kInvalidUserId; // Windows does not support getuid();
+#else
+ int32_t uid = static_cast<int32_t>(getuid());
+#endif
+
+ return SessionData{
+ .compilation_reason = CompilationReason::kUnknown,
+ .compiler_filter = std::nullopt,
+ .session_id = kInvalidSessionId,
+ .uid = uid,
+ };
+}
+
+ArtMetrics::ArtMetrics() : beginning_timestamp_ {MilliTime()}
#define ART_COUNTER(name) \
, name##_ {}
ART_COUNTERS(ART_COUNTER)
@@ -59,6 +76,8 @@
}
void ArtMetrics::ReportAllMetrics(MetricsBackend* backend) const {
+ backend->BeginReport(MilliTime() - beginning_timestamp_);
+
// Dump counters
#define ART_COUNTER(name) name()->Report(backend);
ART_COUNTERS(ART_COUNTER)
@@ -68,34 +87,58 @@
#define ART_HISTOGRAM(name, num_buckets, low_value, high_value) name()->Report(backend);
ART_HISTOGRAMS(ART_HISTOGRAM)
#undef ART_HISTOGRAM
+
+ backend->EndReport();
}
void ArtMetrics::DumpForSigQuit(std::ostream& os) const {
- os << "\n*** ART internal metrics ***\n\n";
- StreamBackend backend{os};
+ StringBackend backend;
ReportAllMetrics(&backend);
- os << "\n*** Done dumping ART internal metrics ***\n";
+ os << backend.GetAndResetBuffer();
}
-StreamBackend::StreamBackend(std::ostream& os) : os_{os} {}
+StringBackend::StringBackend() {}
-void StreamBackend::BeginSession([[maybe_unused]] const SessionData& session_data) {
- // Not needed for now.
+std::string StringBackend::GetAndResetBuffer() {
+ std::string result = os_.str();
+ os_.clear();
+ os_.str("");
+ return result;
}
-void StreamBackend::EndSession() {
- // Not needed for now.
+void StringBackend::BeginSession(const SessionData& session_data) {
+ session_data_ = session_data;
}
-void StreamBackend::ReportCounter(DatumId counter_type, uint64_t value) {
- os_ << DatumName(counter_type) << ": count = " << value << "\n";
+void StringBackend::BeginReport(uint64_t timestamp_since_start_ms) {
+ os_ << "\n*** ART internal metrics ***\n";
+ os_ << " Metadata:\n";
+ os_ << " timestamp_since_start_ms: " << timestamp_since_start_ms << "\n";
+ if (session_data_.has_value()) {
+ os_ << " session_id: " << session_data_->session_id << "\n";
+ os_ << " uid: " << session_data_->uid << "\n";
+ os_ << " compilation_reason: " << CompilationReasonName(session_data_->compilation_reason)
+ << "\n";
+ os_ << " compiler_filter: "
+ << (session_data_->compiler_filter.has_value()
+ ? CompilerFilter::NameOfFilter(session_data_->compiler_filter.value())
+ : "(unspecified)")
+ << "\n";
+ }
+ os_ << " Metrics:\n";
}
-void StreamBackend::ReportHistogram(DatumId histogram_type,
+void StringBackend::EndReport() { os_ << "*** Done dumping ART internal metrics ***\n"; }
+
+void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) {
+ os_ << " " << DatumName(counter_type) << ": count = " << value << "\n";
+}
+
+void StringBackend::ReportHistogram(DatumId histogram_type,
int64_t minimum_value_,
int64_t maximum_value_,
const std::vector<uint32_t>& buckets) {
- os_ << DatumName(histogram_type) << ": range = " << minimum_value_ << "..." << maximum_value_;
+ os_ << " " << DatumName(histogram_type) << ": range = " << minimum_value_ << "..." << maximum_value_;
if (buckets.size() > 0) {
os_ << ", buckets: ";
bool first = true;
@@ -112,6 +155,39 @@
}
}
+LogBackend::LogBackend(android::base::LogSeverity level) : level_{level} {}
+
+void LogBackend::BeginReport(uint64_t timestamp_since_start_ms) {
+ GetAndResetBuffer();
+ StringBackend::BeginReport(timestamp_since_start_ms);
+}
+
+void LogBackend::EndReport() {
+ StringBackend::EndReport();
+ LOG_STREAM(level_) << GetAndResetBuffer();
+}
+
+FileBackend::FileBackend(std::string filename) : filename_{filename} {}
+
+void FileBackend::BeginReport(uint64_t timestamp_since_start_ms) {
+ GetAndResetBuffer();
+ StringBackend::BeginReport(timestamp_since_start_ms);
+}
+
+void FileBackend::EndReport() {
+ StringBackend::EndReport();
+ std::string error_message;
+ auto file{
+ LockedFile::Open(filename_.c_str(), O_CREAT | O_WRONLY | O_APPEND, true, &error_message)};
+ if (file.get() == nullptr) {
+ LOG(WARNING) << "Could open metrics file '" << filename_ << "': " << error_message;
+ } else {
+ if (!android::base::WriteStringToFd(GetAndResetBuffer(), file.get()->Fd())) {
+ PLOG(WARNING) << "Error writing metrics to file";
+ }
+ }
+}
+
} // namespace metrics
} // namespace art
diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc
index f568ea0..4643f14 100644
--- a/libartbase/base/metrics/metrics_test.cc
+++ b/libartbase/base/metrics/metrics_test.cc
@@ -208,20 +208,20 @@
// Makes sure all defined metrics are included when dumping through StreamBackend.
TEST_F(MetricsTest, StreamBackendDumpAllMetrics) {
ArtMetrics metrics;
- std::stringstream os;
- StreamBackend backend(os);
+ StringBackend backend;
metrics.ReportAllMetrics(&backend);
// Make sure the resulting string lists all the counters.
+ const std::string result = backend.GetAndResetBuffer();
#define COUNTER(name) \
- EXPECT_NE(os.str().find(DatumName(DatumId::k##name)), std::string::npos)
+ EXPECT_NE(result.find(DatumName(DatumId::k##name)), std::string::npos)
ART_COUNTERS(COUNTER);
#undef COUNTER
// Make sure the resulting string lists all the histograms.
#define HISTOGRAM(name, num_buckets, minimum_value, maximum_value) \
- EXPECT_NE(os.str().find(DatumName(DatumId::k##name)), std::string::npos)
+ EXPECT_NE(result.find(DatumName(DatumId::k##name)), std::string::npos)
ART_HISTOGRAMS(HISTOGRAM);
#undef HISTOGRAM
}
diff --git a/libartbase/base/metrics/metrics_test.h b/libartbase/base/metrics/metrics_test.h
index 72ca90e..4a7dad8 100644
--- a/libartbase/base/metrics/metrics_test.h
+++ b/libartbase/base/metrics/metrics_test.h
@@ -34,7 +34,8 @@
class TestBackendBase : public MetricsBackend {
public:
void BeginSession([[maybe_unused]] const SessionData& session_data) override {}
- void EndSession() override {}
+
+ void BeginReport([[maybe_unused]] uint64_t timestamp_since_start_ms) override {}
void ReportCounter([[maybe_unused]] DatumId counter_type,
[[maybe_unused]] uint64_t value) override {}
@@ -43,6 +44,8 @@
[[maybe_unused]] int64_t low_value_,
[[maybe_unused]] int64_t high_value,
[[maybe_unused]] const std::vector<uint32_t>& buckets) override {}
+
+ void EndReport() override {}
};
template <DatumId counter_type>