diff options
author | Baligh Uddin <baligh@google.com> | 2013-11-01 15:34:23 -0700 |
---|---|---|
committer | Baligh Uddin <baligh@google.com> | 2013-11-01 15:34:23 -0700 |
commit | fa91a0705ce6651a3f3f20dd55a86842eea65c2e (patch) | |
tree | f6182da0b1d99645f7ead50df30ebf8448ad0164 /runtime/base | |
parent | 1eeba46ba66e462b58640b604b035a481d65d898 (diff) | |
parent | 56cbb5872a5b48815311f2c6a1a3292682a56afa (diff) | |
download | art-fa91a0705ce6651a3f3f20dd55a86842eea65c2e.zip art-fa91a0705ce6651a3f3f20dd55a86842eea65c2e.tar.gz art-fa91a0705ce6651a3f3f20dd55a86842eea65c2e.tar.bz2 |
Merge remote-tracking branch 'origin/kitkat-dev'
Diffstat (limited to 'runtime/base')
40 files changed, 6049 insertions, 0 deletions
diff --git a/runtime/base/bounded_fifo.h b/runtime/base/bounded_fifo.h new file mode 100644 index 0000000..cb92d40 --- /dev/null +++ b/runtime/base/bounded_fifo.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 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_RUNTIME_BASE_BOUNDED_FIFO_H_ +#define ART_RUNTIME_BASE_BOUNDED_FIFO_H_ + +#include "cutils/atomic.h" +#include "cutils/atomic-inline.h" + +namespace art { + +// A bounded fifo is a fifo which has a bounded size. The power of two version uses a bit mask to +// avoid needing to deal with wrapping integers around or using a modulo operation. +template <typename T, const size_t MaxSize> +class BoundedFifoPowerOfTwo { + public: + BoundedFifoPowerOfTwo() { + // TODO: Do this with a compile time check. + CHECK(IsPowerOfTwo(MaxSize)); + clear(); + } + + void clear() { + back_index_ = 0; + size_ = 0; + } + + bool empty() const { + return size() == 0; + } + + size_t size() const { + return size_; + } + + void push_back(const T& value) { + ++size_; + DCHECK_LE(size_, MaxSize); + // Relies on integer overflow behaviour. + data_[back_index_++ & mask_] = value; + } + + const T& front() const { + DCHECK_GT(size_, 0U); + return data_[(back_index_ - size_) & mask_]; + } + + void pop_front() { + DCHECK_GT(size_, 0U); + --size_; + } + + private: + static const size_t mask_ = MaxSize - 1; + size_t back_index_, size_; + T data_[MaxSize]; +}; + +} // namespace art + +#endif // ART_RUNTIME_BASE_BOUNDED_FIFO_H_ diff --git a/runtime/base/casts.h b/runtime/base/casts.h new file mode 100644 index 0000000..be94c2e --- /dev/null +++ b/runtime/base/casts.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011 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_RUNTIME_BASE_CASTS_H_ +#define ART_RUNTIME_BASE_CASTS_H_ + +#include <assert.h> +#include <string.h> +#include "base/macros.h" + +namespace art { + +// Use implicit_cast as a safe version of static_cast or const_cast +// for upcasting in the type hierarchy (i.e. casting a pointer to Foo +// to a pointer to SuperclassOfFoo or casting a pointer to Foo to +// a const pointer to Foo). +// When you use implicit_cast, the compiler checks that the cast is safe. +// Such explicit implicit_casts are necessary in surprisingly many +// situations where C++ demands an exact type match instead of an +// argument type convertable to a target type. +// +// The From type can be inferred, so the preferred syntax for using +// implicit_cast is the same as for static_cast etc.: +// +// implicit_cast<ToType>(expr) +// +// implicit_cast would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +template<typename To, typename From> +inline To implicit_cast(From const &f) { + return f; +} + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use implicit_cast<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. + +template<typename To, typename From> // use like this: down_cast<T*>(foo); +inline To down_cast(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) { + implicit_cast<From*, To>(0); + } + + // + // assert(f == NULL || dynamic_cast<To>(f) != NULL); // RTTI: debug mode only! + return static_cast<To>(f); +} + +template <class Dest, class Source> +inline Dest bit_cast(const Source& source) { + // Compile time assertion: sizeof(Dest) == sizeof(Source) + // A compile error here means your Dest and Source have different sizes. + COMPILE_ASSERT(sizeof(Dest) == sizeof(Source), verify_sizes_are_equal); + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +} // namespace art + +#endif // ART_RUNTIME_BASE_CASTS_H_ diff --git a/runtime/base/histogram-inl.h b/runtime/base/histogram-inl.h new file mode 100644 index 0000000..0345266 --- /dev/null +++ b/runtime/base/histogram-inl.h @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2013 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_RUNTIME_BASE_HISTOGRAM_INL_H_ +#define ART_RUNTIME_BASE_HISTOGRAM_INL_H_ + +#include "histogram.h" + +#include "utils.h" + +#include <algorithm> +#include <cmath> +#include <limits> +#include <ostream> + +namespace art { + +template <class Value> inline void Histogram<Value>::AddValue(Value value) { + CHECK_GE(value, static_cast<Value>(0)); + if (value >= max_) { + Value new_max = ((value + 1) / bucket_width_ + 1) * bucket_width_; + DCHECK_GT(new_max, max_); + GrowBuckets(new_max); + } + + BucketiseValue(value); +} + +template <class Value> +inline Histogram<Value>::Histogram(const char* name, Value initial_bucket_width, + size_t max_buckets) + : kAdjust(1000), + kInitialBucketCount(8), + name_(name), + max_buckets_(max_buckets), + bucket_width_(initial_bucket_width) { + Reset(); +} + +template <class Value> +inline void Histogram<Value>::GrowBuckets(Value new_max) { + while (max_ < new_max) { + // If we have reached the maximum number of buckets, merge buckets together. + if (frequency_.size() >= max_buckets_) { + CHECK(IsAligned<2>(frequency_.size())); + // We double the width of each bucket to reduce the number of buckets by a factor of 2. + bucket_width_ *= 2; + const size_t limit = frequency_.size() / 2; + // Merge the frequencies by adding each adjacent two together. + for (size_t i = 0; i < limit; ++i) { + frequency_[i] = frequency_[i * 2] + frequency_[i * 2 + 1]; + } + // Remove frequencies in the second half of the array which were added to the first half. + while (frequency_.size() > limit) { + frequency_.pop_back(); + } + } + max_ += bucket_width_; + frequency_.push_back(0); + } +} + +template <class Value> inline size_t Histogram<Value>::FindBucket(Value val) const { + // Since this is only a linear histogram, bucket index can be found simply with + // dividing the value by the bucket width. + DCHECK_GE(val, min_); + DCHECK_LE(val, max_); + const size_t bucket_idx = static_cast<size_t>((val - min_) / bucket_width_); + DCHECK_GE(bucket_idx, 0ul); + DCHECK_LE(bucket_idx, GetBucketCount()); + return bucket_idx; +} + +template <class Value> +inline void Histogram<Value>::BucketiseValue(Value val) { + CHECK_LT(val, max_); + sum_ += val; + sum_of_squares_ += val * val; + ++sample_size_; + ++frequency_[FindBucket(val)]; + max_value_added_ = std::max(val, max_value_added_); + min_value_added_ = std::min(val, min_value_added_); +} + +template <class Value> inline void Histogram<Value>::Initialize() { + for (size_t idx = 0; idx < kInitialBucketCount; idx++) { + frequency_.push_back(0); + } + // Cumulative frequency and ranges has a length of 1 over frequency. + max_ = bucket_width_ * GetBucketCount(); +} + +template <class Value> inline size_t Histogram<Value>::GetBucketCount() const { + return frequency_.size(); +} + +template <class Value> inline void Histogram<Value>::Reset() { + sum_of_squares_ = 0; + sample_size_ = 0; + min_ = 0; + sum_ = 0; + min_value_added_ = std::numeric_limits<Value>::max(); + max_value_added_ = std::numeric_limits<Value>::min(); + frequency_.clear(); + Initialize(); +} + +template <class Value> inline Value Histogram<Value>::GetRange(size_t bucket_idx) const { + DCHECK_LE(bucket_idx, GetBucketCount()); + return min_ + bucket_idx * bucket_width_; +} + +template <class Value> inline double Histogram<Value>::Mean() const { + DCHECK_GT(sample_size_, 0ull); + return static_cast<double>(sum_) / static_cast<double>(sample_size_); +} + +template <class Value> inline double Histogram<Value>::Variance() const { + DCHECK_GT(sample_size_, 0ull); + // Using algorithms for calculating variance over a population: + // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + Value sum_squared = sum_ * sum_; + double sum_squared_by_n_squared = + static_cast<double>(sum_squared) / + static_cast<double>(sample_size_ * sample_size_); + double sum_of_squares_by_n = + static_cast<double>(sum_of_squares_) / static_cast<double>(sample_size_); + return sum_of_squares_by_n - sum_squared_by_n_squared; +} + +template <class Value> +inline void Histogram<Value>::PrintBins(std::ostream& os, const CumulativeData& data) const { + DCHECK_GT(sample_size_, 0ull); + for (size_t bin_idx = 0; bin_idx < data.freq_.size(); ++bin_idx) { + if (bin_idx > 0 && data.perc_[bin_idx] == data.perc_[bin_idx - 1]) { + bin_idx++; + continue; + } + os << GetRange(bin_idx) << ": " << data.freq_[bin_idx] << "\t" + << data.perc_[bin_idx] * 100.0 << "%\n"; + } +} + +template <class Value> +inline void Histogram<Value>::PrintConfidenceIntervals(std::ostream &os, double interval, + const CumulativeData& data) const { + DCHECK_GT(interval, 0); + DCHECK_LT(interval, 1.0); + + double per_0 = (1.0 - interval) / 2.0; + double per_1 = per_0 + interval; + os << Name() << ":\t"; + TimeUnit unit = GetAppropriateTimeUnit(Mean() * kAdjust); + os << (interval * 100) << "% C.I. " << FormatDuration(Percentile(per_0, data) * kAdjust, unit); + os << "-" << FormatDuration(Percentile(per_1, data) * kAdjust, unit) << " "; + os << "Avg: " << FormatDuration(Mean() * kAdjust, unit) << " Max: "; + os << FormatDuration(Max() * kAdjust, unit) << "\n"; +} + +template <class Value> inline void Histogram<Value>::CreateHistogram(CumulativeData& out_data) { + DCHECK_GT(sample_size_, 0ull); + out_data.freq_.clear(); + out_data.perc_.clear(); + uint64_t accumulated = 0; + out_data.freq_.push_back(accumulated); + out_data.perc_.push_back(0.0); + for (size_t idx = 0; idx < frequency_.size(); idx++) { + accumulated += frequency_[idx]; + out_data.freq_.push_back(accumulated); + out_data.perc_.push_back(static_cast<double>(accumulated) / static_cast<double>(sample_size_)); + } + DCHECK_EQ(out_data.freq_.back(), sample_size_); + DCHECK_LE(std::abs(out_data.perc_.back() - 1.0), 0.001); +} + +template <class Value> +inline double Histogram<Value>::Percentile(double per, const CumulativeData& data) const { + DCHECK_GT(data.perc_.size(), 0ull); + size_t upper_idx = 0, lower_idx = 0; + for (size_t idx = 0; idx < data.perc_.size(); idx++) { + if (per <= data.perc_[idx]) { + upper_idx = idx; + break; + } + + if (per >= data.perc_[idx] && idx != 0 && data.perc_[idx] != data.perc_[idx - 1]) { + lower_idx = idx; + } + } + + const double lower_perc = data.perc_[lower_idx]; + const double lower_value = static_cast<double>(GetRange(lower_idx)); + if (per == lower_perc) { + return lower_value; + } + + const double upper_perc = data.perc_[upper_idx]; + const double upper_value = static_cast<double>(GetRange(upper_idx)); + if (per == upper_perc) { + return upper_value; + } + DCHECK_GT(upper_perc, lower_perc); + + double value = lower_value + (upper_value - lower_value) * + (per - lower_perc) / (upper_perc - lower_perc); + + if (value < min_value_added_) { + value = min_value_added_; + } else if (value > max_value_added_) { + value = max_value_added_; + } + + return value; +} + +} // namespace art +#endif // ART_RUNTIME_BASE_HISTOGRAM_INL_H_ + diff --git a/runtime/base/histogram.h b/runtime/base/histogram.h new file mode 100644 index 0000000..2a02cf4 --- /dev/null +++ b/runtime/base/histogram.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 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_RUNTIME_BASE_HISTOGRAM_H_ +#define ART_RUNTIME_BASE_HISTOGRAM_H_ + +#include <vector> +#include <string> + +#include "base/logging.h" +#include "utils.h" + +namespace art { + +// Creates a data histogram for a better understanding of statistical data. +// Histogram analysis goes beyond simple mean and standard deviation to provide +// percentiles values, describing where the $% of the input data lies. +// Designed to be simple and used with timing logger in art. + +template <class Value> class Histogram { + const double kAdjust; + const size_t kInitialBucketCount; + + public: + class CumulativeData { + friend class Histogram<Value>; + std::vector<uint64_t> freq_; + std::vector<double> perc_; + }; + + Histogram(const char* name, Value initial_bucket_width, size_t max_buckets = 100); + void AddValue(Value); + // Builds the cumulative distribution function from the frequency data. + // Accumulative summation of frequencies. + // cumulative_freq[i] = sum(frequency[j] : 0 < j < i ) + // Accumulative summation of percentiles; which is the frequency / SampleSize + // cumulative_perc[i] = sum(frequency[j] / SampleSize : 0 < j < i ) + void CreateHistogram(CumulativeData& data); + // Reset the cumulative values, next time CreateHistogram is called it will recreate the cache. + void Reset(); + double Mean() const; + double Variance() const; + double Percentile(double per, const CumulativeData& data) const; + void PrintConfidenceIntervals(std::ostream& os, double interval, + const CumulativeData& data) const; + void PrintBins(std::ostream& os, const CumulativeData& data) const; + Value GetRange(size_t bucket_idx) const; + size_t GetBucketCount() const; + + uint64_t SampleSize() const { + return sample_size_; + } + + Value Sum() const { + return sum_; + } + + Value Min() const { + return min_value_added_; + } + + Value Max() const { + return max_value_added_; + } + + const std::string& Name() const { + return name_; + } + + private: + void Initialize(); + size_t FindBucket(Value val) const; + void BucketiseValue(Value val); + // Add more buckets to the histogram to fill in a new value that exceeded + // the max_read_value_. + void GrowBuckets(Value val); + std::string name_; + // Maximum number of buckets. + const size_t max_buckets_; + // Number of samples placed in histogram. + size_t sample_size_; + // Width of the bucket range. The lower the value is the more accurate + // histogram percentiles are. Grows adaptively when we hit max buckets. + Value bucket_width_; + // How many occurrences of values fall within a bucket at index i where i covers the range + // starting at min_ + i * bucket_width_ with size bucket_size_. + std::vector<uint32_t> frequency_; + // Summation of all the elements inputed by the user. + Value sum_; + // Minimum value that can fit in the histogram. Fixed to zero for now. + Value min_; + // Maximum value that can fit in the histogram, grows adaptively. + Value max_; + // Summation of the values entered. Used to calculate variance. + Value sum_of_squares_; + // Maximum value entered in the histogram. + Value min_value_added_; + // Minimum value entered in the histogram. + Value max_value_added_; + + DISALLOW_COPY_AND_ASSIGN(Histogram); +}; +} // namespace art + +#endif // ART_RUNTIME_BASE_HISTOGRAM_H_ diff --git a/runtime/base/histogram_test.cc b/runtime/base/histogram_test.cc new file mode 100644 index 0000000..534440c --- /dev/null +++ b/runtime/base/histogram_test.cc @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2013 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 "gtest/gtest.h" +#include "histogram-inl.h" +#include "UniquePtr.h" + +#include <sstream> + +namespace art { + +// Simple usage: +// Histogram *hist(new Histogram("SimplePercentiles")); +// Percentile PerValue +// hist->AddValue(121); +// hist->AddValue(132); +// hist->AddValue(140); +// hist->AddValue(145); +// hist->AddValue(155); +// hist->CreateHistogram(); +// PerValue = hist->PercentileVal(0.50); finds the 50th percentile(median). + +TEST(Histtest, MeanTest) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("MeanTest", 5)); + + double mean; + for (size_t Idx = 0; Idx < 90; Idx++) { + hist->AddValue(static_cast<uint64_t>(50)); + } + mean = hist->Mean(); + EXPECT_EQ(mean, 50); + hist->Reset(); + hist->AddValue(9); + hist->AddValue(17); + hist->AddValue(28); + hist->AddValue(28); + mean = hist->Mean(); + EXPECT_EQ(20.5, mean); +} + +TEST(Histtest, VarianceTest) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("VarianceTest", 5)); + + double variance; + hist->AddValue(9); + hist->AddValue(17); + hist->AddValue(28); + hist->AddValue(28); + variance = hist->Variance(); + EXPECT_EQ(64.25, variance); +} + +TEST(Histtest, Percentile) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("Percentile", 5)); + Histogram<uint64_t>::CumulativeData data; + + double PerValue; + + hist->AddValue(20); + hist->AddValue(31); + hist->AddValue(42); + hist->AddValue(50); + hist->AddValue(60); + hist->AddValue(70); + + hist->AddValue(98); + + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); + hist->AddValue(145); + hist->AddValue(155); + + hist->CreateHistogram(data); + PerValue = hist->Percentile(0.50, data); + EXPECT_EQ(875, static_cast<int>(PerValue * 10)); +} + +TEST(Histtest, UpdateRange) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("UpdateRange", 5)); + Histogram<uint64_t>::CumulativeData data; + + double PerValue; + + hist->AddValue(15); + hist->AddValue(17); + hist->AddValue(35); + hist->AddValue(50); + hist->AddValue(68); + hist->AddValue(75); + hist->AddValue(93); + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); // Median value + hist->AddValue(145); + hist->AddValue(155); + hist->AddValue(163); + hist->AddValue(168); + hist->AddValue(175); + hist->AddValue(182); + hist->AddValue(193); + hist->AddValue(200); + hist->AddValue(205); + hist->AddValue(212); + hist->CreateHistogram(data); + PerValue = hist->Percentile(0.50, data); + + std::string text; + std::stringstream stream; + std::string expected("UpdateRange:\t99% C.I. 15us-212us Avg: 126.380us Max: 212us\n"); + hist->PrintConfidenceIntervals(stream, 0.99, data); + + EXPECT_EQ(expected, stream.str()); + EXPECT_GE(PerValue, 132); + EXPECT_LE(PerValue, 145); +} + +TEST(Histtest, Reset) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("Reset", 5)); + Histogram<uint64_t>::CumulativeData data; + + double PerValue; + hist->AddValue(0); + hist->AddValue(189); + hist->AddValue(389); + hist->Reset(); + hist->AddValue(15); + hist->AddValue(17); + hist->AddValue(35); + hist->AddValue(50); + hist->AddValue(68); + hist->AddValue(75); + hist->AddValue(93); + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); // Median value + hist->AddValue(145); + hist->AddValue(155); + hist->AddValue(163); + hist->AddValue(168); + hist->AddValue(175); + hist->AddValue(182); + hist->AddValue(193); + hist->AddValue(200); + hist->AddValue(205); + hist->AddValue(212); + hist->CreateHistogram(data); + PerValue = hist->Percentile(0.50, data); + + std::string text; + std::stringstream stream; + std::string expected("Reset:\t99% C.I. 15us-212us Avg: 126.380us Max: 212us\n"); + hist->PrintConfidenceIntervals(stream, 0.99, data); + + EXPECT_EQ(expected, stream.str()); + EXPECT_GE(PerValue, 132); + EXPECT_LE(PerValue, 145); +} + +TEST(Histtest, MultipleCreateHist) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("MultipleCreateHist", 5)); + Histogram<uint64_t>::CumulativeData data; + + double PerValue; + hist->AddValue(15); + hist->AddValue(17); + hist->AddValue(35); + hist->AddValue(50); + hist->AddValue(68); + hist->AddValue(75); + hist->AddValue(93); + hist->CreateHistogram(data); + hist->AddValue(110); + hist->AddValue(121); + hist->AddValue(132); + hist->AddValue(140); // Median value + hist->AddValue(145); + hist->AddValue(155); + hist->AddValue(163); + hist->AddValue(168); + hist->CreateHistogram(data); + hist->AddValue(175); + hist->AddValue(182); + hist->AddValue(193); + hist->AddValue(200); + hist->AddValue(205); + hist->AddValue(212); + hist->CreateHistogram(data); + PerValue = hist->Percentile(0.50, data); + std::stringstream stream; + std::string expected("MultipleCreateHist:\t99% C.I. 15us-212us Avg: 126.380us Max: 212us\n"); + hist->PrintConfidenceIntervals(stream, 0.99, data); + + EXPECT_EQ(expected, stream.str()); + EXPECT_GE(PerValue, 132); + EXPECT_LE(PerValue, 145); +} + +TEST(Histtest, SingleValue) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("SingleValue", 5)); + Histogram<uint64_t>::CumulativeData data; + + hist->AddValue(1); + hist->CreateHistogram(data); + std::stringstream stream; + std::string expected = "SingleValue:\t99% C.I. 1us-1us Avg: 1us Max: 1us\n"; + hist->PrintConfidenceIntervals(stream, 0.99, data); + EXPECT_EQ(expected, stream.str()); +} + +TEST(Histtest, CappingPercentiles) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("CappingPercentiles", 5)); + Histogram<uint64_t>::CumulativeData data; + + double per_995; + double per_005; + // All values are similar. + for (uint64_t idx = 0ull; idx < 150ull; idx++) { + hist->AddValue(0); + } + hist->CreateHistogram(data); + per_995 = hist->Percentile(0.995, data); + EXPECT_EQ(per_995, 0); + hist->Reset(); + for (size_t idx = 0; idx < 200; idx++) { + for (uint64_t val = 1ull; val <= 4ull; val++) { + hist->AddValue(val); + } + } + hist->CreateHistogram(data); + per_005 = hist->Percentile(0.005, data); + per_995 = hist->Percentile(0.995, data); + EXPECT_EQ(1, per_005); + EXPECT_EQ(4, per_995); +} + +TEST(Histtest, SpikyValues) { + UniquePtr<Histogram<uint64_t> > hist(new Histogram<uint64_t>("SpikyValues", 5, 4096)); + Histogram<uint64_t>::CumulativeData data; + + for (uint64_t idx = 0ull; idx < 30ull; idx++) { + for (uint64_t idx_inner = 0ull; idx_inner < 5ull; idx_inner++) { + hist->AddValue(idx * idx_inner); + } + } + hist->AddValue(10000); + hist->CreateHistogram(data); + std::stringstream stream; + std::string expected = "SpikyValues:\t99% C.I. 0.089us-2541.825us Avg: 95.033us Max: 10000us\n"; + hist->PrintConfidenceIntervals(stream, 0.99, data); + EXPECT_EQ(expected, stream.str()); +} + +} // namespace art diff --git a/runtime/base/logging.cc b/runtime/base/logging.cc new file mode 100644 index 0000000..7d54baf --- /dev/null +++ b/runtime/base/logging.cc @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2011 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 "logging.h" + +#include "base/mutex.h" +#include "runtime.h" +#include "thread.h" +#include "utils.h" + +namespace art { + +LogVerbosity gLogVerbosity; + +unsigned int gAborting = 0; + +static LogSeverity gMinimumLogSeverity = INFO; +static std::string* gCmdLine = NULL; +static std::string* gProgramInvocationName = NULL; +static std::string* gProgramInvocationShortName = NULL; + +const char* GetCmdLine() { + return (gCmdLine != NULL) ? gCmdLine->c_str() : NULL; +} + +const char* ProgramInvocationName() { + return (gProgramInvocationName != NULL) ? gProgramInvocationName->c_str() : "art"; +} + +const char* ProgramInvocationShortName() { + return (gProgramInvocationShortName != NULL) ? gProgramInvocationShortName->c_str() : "art"; +} + +// Configure logging based on ANDROID_LOG_TAGS environment variable. +// We need to parse a string that looks like +// +// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i +// +// The tag (or '*' for the global level) comes first, followed by a colon +// and a letter indicating the minimum priority level we're expected to log. +// This can be used to reveal or conceal logs with specific tags. +void InitLogging(char* argv[]) { + if (gCmdLine != NULL) { + return; + } + // TODO: Move this to a more obvious InitART... + Locks::Init(); + + // Stash the command line for later use. We can use /proc/self/cmdline on Linux to recover this, + // but we don't have that luxury on the Mac, and there are a couple of argv[0] variants that are + // commonly used. + if (argv != NULL) { + gCmdLine = new std::string(argv[0]); + for (size_t i = 1; argv[i] != NULL; ++i) { + gCmdLine->append(" "); + gCmdLine->append(argv[i]); + } + gProgramInvocationName = new std::string(argv[0]); + const char* last_slash = strrchr(argv[0], '/'); + gProgramInvocationShortName = new std::string((last_slash != NULL) ? last_slash + 1 : argv[0]); + } else { + // TODO: fall back to /proc/self/cmdline when argv is NULL on Linux + gCmdLine = new std::string("<unset>"); + } + const char* tags = getenv("ANDROID_LOG_TAGS"); + if (tags == NULL) { + return; + } + + std::vector<std::string> specs; + Split(tags, ' ', specs); + for (size_t i = 0; i < specs.size(); ++i) { + // "tag-pattern:[vdiwefs]" + std::string spec(specs[i]); + if (spec.size() == 3 && StartsWith(spec, "*:")) { + switch (spec[2]) { + case 'v': + gMinimumLogSeverity = VERBOSE; + continue; + case 'd': + gMinimumLogSeverity = DEBUG; + continue; + case 'i': + gMinimumLogSeverity = INFO; + continue; + case 'w': + gMinimumLogSeverity = WARNING; + continue; + case 'e': + gMinimumLogSeverity = ERROR; + continue; + case 'f': + gMinimumLogSeverity = FATAL; + continue; + // liblog will even suppress FATAL if you say 's' for silent, but that's crazy! + case 's': + gMinimumLogSeverity = FATAL; + continue; + } + } + LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags << ")"; + } +} + +LogMessageData::LogMessageData(const char* file, int line, LogSeverity severity, int error) + : file(file), + line_number(line), + severity(severity), + error(error) { + const char* last_slash = strrchr(file, '/'); + file = (last_slash == NULL) ? file : last_slash + 1; +} + +LogMessage::~LogMessage() { + if (data_->severity < gMinimumLogSeverity) { + return; // No need to format something we're not going to output. + } + + // Finish constructing the message. + if (data_->error != -1) { + data_->buffer << ": " << strerror(data_->error); + } + std::string msg(data_->buffer.str()); + + // Do the actual logging with the lock held. + { + MutexLock mu(Thread::Current(), *Locks::logging_lock_); + if (msg.find('\n') == std::string::npos) { + LogLine(*data_, msg.c_str()); + } else { + msg += '\n'; + size_t i = 0; + while (i < msg.size()) { + size_t nl = msg.find('\n', i); + msg[nl] = '\0'; + LogLine(*data_, &msg[i]); + i = nl + 1; + } + } + } + + // Abort if necessary. + if (data_->severity == FATAL) { + Runtime::Abort(); + } +} + +HexDump::HexDump(const void* address, size_t byte_count, bool show_actual_addresses) + : address_(address), byte_count_(byte_count), show_actual_addresses_(show_actual_addresses) { +} + +void HexDump::Dump(std::ostream& os) const { + if (byte_count_ == 0) { + return; + } + + if (address_ == NULL) { + os << "00000000:"; + return; + } + + static const char gHexDigit[] = "0123456789abcdef"; + const unsigned char* addr = reinterpret_cast<const unsigned char*>(address_); + char out[76]; /* exact fit */ + unsigned int offset; /* offset to show while printing */ + + if (show_actual_addresses_) { + offset = reinterpret_cast<int>(addr); + } else { + offset = 0; + } + memset(out, ' ', sizeof(out)-1); + out[8] = ':'; + out[sizeof(out)-1] = '\0'; + + size_t byte_count = byte_count_; + int gap = static_cast<int>(offset & 0x0f); + while (byte_count) { + unsigned int line_offset = offset & ~0x0f; + + char* hex = out; + char* asc = out + 59; + + for (int i = 0; i < 8; i++) { + *hex++ = gHexDigit[line_offset >> 28]; + line_offset <<= 4; + } + hex++; + hex++; + + int count = std::min(static_cast<int>(byte_count), 16 - gap); + CHECK_NE(count, 0); + CHECK_LE(count + gap, 16); + + if (gap) { + /* only on first line */ + hex += gap * 3; + asc += gap; + } + + int i; + for (i = gap ; i < count+gap; i++) { + *hex++ = gHexDigit[*addr >> 4]; + *hex++ = gHexDigit[*addr & 0x0f]; + hex++; + if (*addr >= 0x20 && *addr < 0x7f /*isprint(*addr)*/) { + *asc++ = *addr; + } else { + *asc++ = '.'; + } + addr++; + } + for (; i < 16; i++) { + /* erase extra stuff; only happens on last line */ + *hex++ = ' '; + *hex++ = ' '; + hex++; + *asc++ = ' '; + } + + os << out; + + gap = 0; + byte_count -= count; + offset += count; + } +} + +std::ostream& operator<<(std::ostream& os, const HexDump& rhs) { + rhs.Dump(os); + return os; +} + +} // namespace art diff --git a/runtime/base/logging.h b/runtime/base/logging.h new file mode 100644 index 0000000..8e40da0 --- /dev/null +++ b/runtime/base/logging.h @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2011 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_RUNTIME_BASE_LOGGING_H_ +#define ART_RUNTIME_BASE_LOGGING_H_ + +#include <cerrno> +#include <cstring> +#include <iostream> // NOLINT +#include <sstream> +#include <signal.h> +#include "base/macros.h" +#include "log_severity.h" +#include "UniquePtr.h" + +#define CHECK(x) \ + if (UNLIKELY(!(x))) \ + ::art::LogMessage(__FILE__, __LINE__, FATAL, -1).stream() \ + << "Check failed: " #x << " " + +#define CHECK_OP(LHS, RHS, OP) \ + for (auto _values = ::art::MakeEagerEvaluator(LHS, RHS); \ + UNLIKELY(!(_values.lhs OP _values.rhs)); /* empty */) \ + ::art::LogMessage(__FILE__, __LINE__, FATAL, -1).stream() \ + << "Check failed: " << #LHS << " " << #OP << " " << #RHS \ + << " (" #LHS "=" << _values.lhs << ", " #RHS "=" << _values.rhs << ") " + +#define CHECK_EQ(x, y) CHECK_OP(x, y, ==) +#define CHECK_NE(x, y) CHECK_OP(x, y, !=) +#define CHECK_LE(x, y) CHECK_OP(x, y, <=) +#define CHECK_LT(x, y) CHECK_OP(x, y, <) +#define CHECK_GE(x, y) CHECK_OP(x, y, >=) +#define CHECK_GT(x, y) CHECK_OP(x, y, >) + +#define CHECK_STROP(s1, s2, sense) \ + if (UNLIKELY((strcmp(s1, s2) == 0) != sense)) \ + LOG(FATAL) << "Check failed: " \ + << "\"" << s1 << "\"" \ + << (sense ? " == " : " != ") \ + << "\"" << s2 << "\"" + +#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true) +#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false) + +#define CHECK_PTHREAD_CALL(call, args, what) \ + do { \ + int rc = call args; \ + if (rc != 0) { \ + errno = rc; \ + PLOG(FATAL) << # call << " failed for " << what; \ + } \ + } while (false) + +#ifndef NDEBUG + +#define DCHECK(x) CHECK(x) +#define DCHECK_EQ(x, y) CHECK_EQ(x, y) +#define DCHECK_NE(x, y) CHECK_NE(x, y) +#define DCHECK_LE(x, y) CHECK_LE(x, y) +#define DCHECK_LT(x, y) CHECK_LT(x, y) +#define DCHECK_GE(x, y) CHECK_GE(x, y) +#define DCHECK_GT(x, y) CHECK_GT(x, y) +#define DCHECK_STREQ(s1, s2) CHECK_STREQ(s1, s2) +#define DCHECK_STRNE(s1, s2) CHECK_STRNE(s1, s2) + +#else // NDEBUG + +#define DCHECK(condition) \ + while (false) \ + CHECK(condition) + +#define DCHECK_EQ(val1, val2) \ + while (false) \ + CHECK_EQ(val1, val2) + +#define DCHECK_NE(val1, val2) \ + while (false) \ + CHECK_NE(val1, val2) + +#define DCHECK_LE(val1, val2) \ + while (false) \ + CHECK_LE(val1, val2) + +#define DCHECK_LT(val1, val2) \ + while (false) \ + CHECK_LT(val1, val2) + +#define DCHECK_GE(val1, val2) \ + while (false) \ + CHECK_GE(val1, val2) + +#define DCHECK_GT(val1, val2) \ + while (false) \ + CHECK_GT(val1, val2) + +#define DCHECK_STREQ(str1, str2) \ + while (false) \ + CHECK_STREQ(str1, str2) + +#define DCHECK_STRNE(str1, str2) \ + while (false) \ + CHECK_STRNE(str1, str2) + +#endif + +#define LOG(severity) ::art::LogMessage(__FILE__, __LINE__, severity, -1).stream() +#define PLOG(severity) ::art::LogMessage(__FILE__, __LINE__, severity, errno).stream() + +#define LG LOG(INFO) + +#define UNIMPLEMENTED(level) LOG(level) << __PRETTY_FUNCTION__ << " unimplemented " + +#define VLOG_IS_ON(module) UNLIKELY(::art::gLogVerbosity.module) +#define VLOG(module) if (VLOG_IS_ON(module)) ::art::LogMessage(__FILE__, __LINE__, INFO, -1).stream() +#define VLOG_STREAM(module) ::art::LogMessage(__FILE__, __LINE__, INFO, -1).stream() + +// +// Implementation details beyond this point. +// + +namespace art { + +template <typename LHS, typename RHS> +struct EagerEvaluator { + EagerEvaluator(LHS lhs, RHS rhs) : lhs(lhs), rhs(rhs) { } + LHS lhs; + RHS rhs; +}; + +// We want char*s to be treated as pointers, not strings. If you want them treated like strings, +// you'd need to use CHECK_STREQ and CHECK_STRNE anyway to compare the characters rather than their +// addresses. We could express this more succinctly with std::remove_const, but this is quick and +// easy to understand, and works before we have C++0x. We rely on signed/unsigned warnings to +// protect you against combinations not explicitly listed below. +#define EAGER_PTR_EVALUATOR(T1, T2) \ + template <> struct EagerEvaluator<T1, T2> { \ + EagerEvaluator(T1 lhs, T2 rhs) \ + : lhs(reinterpret_cast<const void*>(lhs)), \ + rhs(reinterpret_cast<const void*>(rhs)) { } \ + const void* lhs; \ + const void* rhs; \ + } +EAGER_PTR_EVALUATOR(const char*, const char*); +EAGER_PTR_EVALUATOR(const char*, char*); +EAGER_PTR_EVALUATOR(char*, const char*); +EAGER_PTR_EVALUATOR(char*, char*); +EAGER_PTR_EVALUATOR(const unsigned char*, const unsigned char*); +EAGER_PTR_EVALUATOR(const unsigned char*, unsigned char*); +EAGER_PTR_EVALUATOR(unsigned char*, const unsigned char*); +EAGER_PTR_EVALUATOR(unsigned char*, unsigned char*); +EAGER_PTR_EVALUATOR(const signed char*, const signed char*); +EAGER_PTR_EVALUATOR(const signed char*, signed char*); +EAGER_PTR_EVALUATOR(signed char*, const signed char*); +EAGER_PTR_EVALUATOR(signed char*, signed char*); + +template <typename LHS, typename RHS> +EagerEvaluator<LHS, RHS> MakeEagerEvaluator(LHS lhs, RHS rhs) { + return EagerEvaluator<LHS, RHS>(lhs, rhs); +} + +// This indirection greatly reduces the stack impact of having +// lots of checks/logging in a function. +struct LogMessageData { + public: + LogMessageData(const char* file, int line, LogSeverity severity, int error); + std::ostringstream buffer; + const char* const file; + const int line_number; + const LogSeverity severity; + const int error; + + private: + DISALLOW_COPY_AND_ASSIGN(LogMessageData); +}; + +class LogMessage { + public: + LogMessage(const char* file, int line, LogSeverity severity, int error) + : data_(new LogMessageData(file, line, severity, error)) { + } + + ~LogMessage() LOCKS_EXCLUDED(Locks::logging_lock_); + + std::ostream& stream() { + return data_->buffer; + } + + private: + static void LogLine(const LogMessageData& data, const char*); + + const UniquePtr<LogMessageData> data_; + + friend void HandleUnexpectedSignal(int signal_number, siginfo_t* info, void* raw_context); + friend class Mutex; + DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// Prints a hex dump in this format: +// +// 01234560: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef +// 01234568: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef +class HexDump { + public: + HexDump(const void* address, size_t byte_count, bool show_actual_addresses = false); + void Dump(std::ostream& os) const; + + private: + const void* address_; + size_t byte_count_; + bool show_actual_addresses_; + + DISALLOW_COPY_AND_ASSIGN(HexDump); +}; +std::ostream& operator<<(std::ostream& os, const HexDump& rhs); + +// A convenience to allow any class with a "Dump(std::ostream& os)" member function +// but without an operator<< to be used as if it had an operator<<. Use like this: +// +// os << Dumpable<MyType>(my_type_instance); +// +template<typename T> +class Dumpable { + public: + explicit Dumpable(T& value) : value_(value) { + } + + void Dump(std::ostream& os) const { + value_.Dump(os); + } + + private: + T& value_; + + DISALLOW_COPY_AND_ASSIGN(Dumpable); +}; + +template<typename T> +std::ostream& operator<<(std::ostream& os, const Dumpable<T>& rhs) { + rhs.Dump(os); + return os; +} + +template<typename T> +class MutatorLockedDumpable { + public: + explicit MutatorLockedDumpable(T& value) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) : value_(value) { + } + + void Dump(std::ostream& os) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + value_.Dump(os); + } + + private: + T& value_; + + DISALLOW_COPY_AND_ASSIGN(MutatorLockedDumpable); +}; + +template<typename T> +std::ostream& operator<<(std::ostream& os, const MutatorLockedDumpable<T>& rhs) +// TODO: should be SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) however annotalysis +// currently fails for this. + NO_THREAD_SAFETY_ANALYSIS { + rhs.Dump(os); + return os; +} + +// Helps you use operator<< in a const char*-like context such as our various 'F' methods with +// format strings. +template<typename T> +class ToStr { + public: + explicit ToStr(const T& value) { + std::ostringstream os; + os << value; + s_ = os.str(); + } + + const char* c_str() const { + return s_.c_str(); + } + + const std::string& str() const { + return s_; + } + + private: + std::string s_; + DISALLOW_COPY_AND_ASSIGN(ToStr); +}; + +// The members of this struct are the valid arguments to VLOG and VLOG_IS_ON in code, +// and the "-verbose:" command line argument. +struct LogVerbosity { + bool class_linker; // Enabled with "-verbose:class". + bool verifier; + bool compiler; + bool heap; + bool gc; + bool jdwp; + bool jni; + bool monitor; + bool startup; + bool third_party_jni; // Enabled with "-verbose:third-party-jni". + bool threads; +}; + +extern LogVerbosity gLogVerbosity; + +// Used on fatal exit. Prevents recursive aborts. Allows us to disable +// some error checking to ensure fatal shutdown makes forward progress. +extern unsigned int gAborting; + +extern void InitLogging(char* argv[]); + +extern const char* GetCmdLine(); +extern const char* ProgramInvocationName(); +extern const char* ProgramInvocationShortName(); + +} // namespace art + +#endif // ART_RUNTIME_BASE_LOGGING_H_ diff --git a/runtime/base/logging_android.cc b/runtime/base/logging_android.cc new file mode 100644 index 0000000..9b1ac58 --- /dev/null +++ b/runtime/base/logging_android.cc @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 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 "logging.h" + +#include <unistd.h> + +#include <iostream> + +#include "base/stringprintf.h" +#include "cutils/log.h" + +namespace art { + +static const int kLogSeverityToAndroidLogPriority[] = { + ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, ANDROID_LOG_FATAL, ANDROID_LOG_FATAL +}; + +void LogMessage::LogLine(const LogMessageData& data, const char* message) { + const char* tag = ProgramInvocationShortName(); + int priority = kLogSeverityToAndroidLogPriority[data.severity]; + if (priority == ANDROID_LOG_FATAL) { + LOG_PRI(priority, tag, "%s:%d] %s", data.file, data.line_number, message); + } else { + LOG_PRI(priority, tag, "%s", message); + } +} + +} // namespace art diff --git a/runtime/base/logging_linux.cc b/runtime/base/logging_linux.cc new file mode 100644 index 0000000..0399128 --- /dev/null +++ b/runtime/base/logging_linux.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 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 "logging.h" + +#include <sys/types.h> +#include <unistd.h> + +#include <cstdio> +#include <cstring> +#include <iostream> + +#include "base/stringprintf.h" +#include "utils.h" + +namespace art { + +void LogMessage::LogLine(const LogMessageData& data, const char* message) { + char severity = "VDIWEFF"[data.severity]; + fprintf(stderr, "%s %c %5d %5d %s:%d] %s\n", + ProgramInvocationShortName(), severity, getpid(), ::art::GetTid(), + data.file, data.line_number, message); +} + +} // namespace art diff --git a/runtime/base/macros.h b/runtime/base/macros.h new file mode 100644 index 0000000..6531858 --- /dev/null +++ b/runtime/base/macros.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2010 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_RUNTIME_BASE_MACROS_H_ +#define ART_RUNTIME_BASE_MACROS_H_ + +#include <stddef.h> // for size_t + +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + +// The COMPILE_ASSERT macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// COMPILE_ASSERT(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template <bool> +struct CompileAssert { +}; + +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] // NOLINT + +// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. +// It goes in the private: declarations in a class. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +// ARRAYSIZE_UNSAFE performs essentially the same calculation as arraysize, +// but can be used on anonymous types or types defined inside +// functions. It's less safe than arraysize as it accepts some +// (although not all) pointers. Therefore, you should use arraysize +// whenever possible. +// +// The expression ARRAYSIZE_UNSAFE(a) is a compile-time constant of type +// size_t. +// +// ARRAYSIZE_UNSAFE catches a few type errors. If you see a compiler error +// +// "warning: division by zero in ..." +// +// when using ARRAYSIZE_UNSAFE, you are (wrongfully) giving it a pointer. +// You should only use ARRAYSIZE_UNSAFE on statically allocated arrays. +// +// The following comments are on the implementation details, and can +// be ignored by the users. +// +// ARRAYSIZE_UNSAFE(arr) works by inspecting sizeof(arr) (the # of bytes in +// the array) and sizeof(*(arr)) (the # of bytes in one array +// element). If the former is divisible by the latter, perhaps arr is +// indeed an array, in which case the division result is the # of +// elements in the array. Otherwise, arr cannot possibly be an array, +// and we generate a compiler error to prevent the code from +// compiling. +// +// Since the size of bool is implementation-defined, we need to cast +// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final +// result has type size_t. +// +// This macro is not perfect as it wrongfully accepts certain +// pointers, namely where the pointer size is divisible by the pointee +// size. Since all our code has to go through a 32-bit compiler, +// where a pointer is 4 bytes, this means all pointers to a type whose +// size is 3 or greater than 4 will be (righteously) rejected. +#define ARRAYSIZE_UNSAFE(a) \ + ((sizeof(a) / sizeof(*(a))) / static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) + +#define SIZEOF_MEMBER(t, f) sizeof((reinterpret_cast<t*>(4096))->f) + +#define OFFSETOF_MEMBER(t, f) \ + (reinterpret_cast<const char*>(&reinterpret_cast<t*>(16)->f) - reinterpret_cast<const char*>(16)) // NOLINT + +#define OFFSETOF_VOLATILE_MEMBER(t, f) \ + (reinterpret_cast<volatile char*>(&reinterpret_cast<t*>(16)->f) - reinterpret_cast<volatile char*>(16)) // NOLINT + +#define PACKED(x) __attribute__ ((__aligned__(x), __packed__)) + +#define LIKELY(x) __builtin_expect((x), true) +#define UNLIKELY(x) __builtin_expect((x), false) + +#ifndef NDEBUG +#define ALWAYS_INLINE +#else +#define ALWAYS_INLINE __attribute__ ((always_inline)) +#endif + +#if defined (__APPLE__) +#define HOT_ATTR +#else +#define HOT_ATTR __attribute__ ((hot)) +#endif + +#define PURE __attribute__ ((__pure__)) + +// bionic and glibc both have TEMP_FAILURE_RETRY, but Mac OS' libc doesn't. +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) ({ \ + typeof(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif + +template<typename T> void UNUSED(const T&) {} + +#if defined(__SUPPORT_TS_ANNOTATION__) + +#define ACQUIRED_AFTER(...) __attribute__ ((acquired_after(__VA_ARGS__))) +#define ACQUIRED_BEFORE(...) __attribute__ ((acquired_before(__VA_ARGS__))) +#define EXCLUSIVE_LOCK_FUNCTION(...) __attribute__ ((exclusive_lock(__VA_ARGS__))) +#define EXCLUSIVE_LOCKS_REQUIRED(...) __attribute__ ((exclusive_locks_required(__VA_ARGS__))) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) __attribute__ ((exclusive_trylock(__VA_ARGS__))) +#define GUARDED_BY(x) __attribute__ ((guarded_by(x))) +#define GUARDED_VAR __attribute__ ((guarded)) +#define LOCKABLE __attribute__ ((lockable)) +#define LOCK_RETURNED(x) __attribute__ ((lock_returned(x))) +#define LOCKS_EXCLUDED(...) __attribute__ ((locks_excluded(__VA_ARGS__))) +#define NO_THREAD_SAFETY_ANALYSIS __attribute__ ((no_thread_safety_analysis)) +#define PT_GUARDED_BY(x) __attribute__ ((point_to_guarded_by(x))) +#define PT_GUARDED_VAR __attribute__ ((point_to_guarded)) +#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable)) +#define SHARED_LOCK_FUNCTION(...) __attribute__ ((shared_lock(__VA_ARGS__))) +#define SHARED_LOCKS_REQUIRED(...) __attribute__ ((shared_locks_required(__VA_ARGS__))) +#define SHARED_TRYLOCK_FUNCTION(...) __attribute__ ((shared_trylock(__VA_ARGS__))) +#define UNLOCK_FUNCTION(...) __attribute__ ((unlock(__VA_ARGS__))) + +#else + +#define ACQUIRED_AFTER(...) +#define ACQUIRED_BEFORE(...) +#define EXCLUSIVE_LOCK_FUNCTION(...) +#define EXCLUSIVE_LOCKS_REQUIRED(...) +#define EXCLUSIVE_TRYLOCK_FUNCTION(...) +#define GUARDED_BY(x) +#define GUARDED_VAR +#define LOCKABLE +#define LOCK_RETURNED(x) +#define LOCKS_EXCLUDED(...) +#define NO_THREAD_SAFETY_ANALYSIS +#define PT_GUARDED_BY(x) +#define PT_GUARDED_VAR +#define SCOPED_LOCKABLE +#define SHARED_LOCK_FUNCTION(...) +#define SHARED_LOCKS_REQUIRED(...) +#define SHARED_TRYLOCK_FUNCTION(...) +#define UNLOCK_FUNCTION(...) + +#endif // defined(__SUPPORT_TS_ANNOTATION__) + +#endif // ART_RUNTIME_BASE_MACROS_H_ diff --git a/runtime/base/mutex-inl.h b/runtime/base/mutex-inl.h new file mode 100644 index 0000000..7e8365e --- /dev/null +++ b/runtime/base/mutex-inl.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2011 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_RUNTIME_BASE_MUTEX_INL_H_ +#define ART_RUNTIME_BASE_MUTEX_INL_H_ + +#include "mutex.h" + +#define ATRACE_TAG ATRACE_TAG_DALVIK + +#include "cutils/atomic-inline.h" +#include "cutils/trace.h" +#include "runtime.h" +#include "thread.h" + +namespace art { + +#define CHECK_MUTEX_CALL(call, args) CHECK_PTHREAD_CALL(call, args, name_) + +#if ART_USE_FUTEXES +#include "linux/futex.h" +#include "sys/syscall.h" +#ifndef SYS_futex +#define SYS_futex __NR_futex +#endif +static inline int futex(volatile int *uaddr, int op, int val, const struct timespec *timeout, volatile int *uaddr2, int val3) { + return syscall(SYS_futex, uaddr, op, val, timeout, uaddr2, val3); +} +#endif // ART_USE_FUTEXES + +class ScopedContentionRecorder { + public: + ScopedContentionRecorder(BaseMutex* mutex, uint64_t blocked_tid, uint64_t owner_tid) + : mutex_(kLogLockContentions ? mutex : NULL), + blocked_tid_(kLogLockContentions ? blocked_tid : 0), + owner_tid_(kLogLockContentions ? owner_tid : 0), + start_nano_time_(kLogLockContentions ? NanoTime() : 0) { + std::string msg = StringPrintf("Lock contention on %s (owner tid: %llu)", + mutex->GetName(), owner_tid); + ATRACE_BEGIN(msg.c_str()); + } + + ~ScopedContentionRecorder() { + ATRACE_END(); + if (kLogLockContentions) { + uint64_t end_nano_time = NanoTime(); + mutex_->RecordContention(blocked_tid_, owner_tid_, end_nano_time - start_nano_time_); + } + } + + private: + BaseMutex* const mutex_; + const uint64_t blocked_tid_; + const uint64_t owner_tid_; + const uint64_t start_nano_time_; +}; + +static inline uint64_t SafeGetTid(const Thread* self) { + if (self != NULL) { + return static_cast<uint64_t>(self->GetTid()); + } else { + return static_cast<uint64_t>(GetTid()); + } +} + +static inline void CheckUnattachedThread(LockLevel level) NO_THREAD_SAFETY_ANALYSIS { + // The check below enumerates the cases where we expect not to be able to sanity check locks + // on a thread. Lock checking is disabled to avoid deadlock when checking shutdown lock. + // TODO: tighten this check. + if (kDebugLocking) { + Runtime* runtime = Runtime::Current(); + CHECK(runtime == NULL || !runtime->IsStarted() || runtime->IsShuttingDown() || + level == kDefaultMutexLevel || level == kRuntimeShutdownLock || + level == kThreadListLock || level == kLoggingLock || level == kAbortLock); + } +} + +inline void BaseMutex::RegisterAsLocked(Thread* self) { + if (UNLIKELY(self == NULL)) { + CheckUnattachedThread(level_); + return; + } + if (kDebugLocking) { + // Check if a bad Mutex of this level or lower is held. + bool bad_mutexes_held = false; + for (int i = level_; i >= 0; --i) { + BaseMutex* held_mutex = self->GetHeldMutex(static_cast<LockLevel>(i)); + if (UNLIKELY(held_mutex != NULL)) { + LOG(ERROR) << "Lock level violation: holding \"" << held_mutex->name_ << "\" " + << "(level " << LockLevel(i) << " - " << i + << ") while locking \"" << name_ << "\" " + << "(level " << level_ << " - " << static_cast<int>(level_) << ")"; + if (i > kAbortLock) { + // Only abort in the check below if this is more than abort level lock. + bad_mutexes_held = true; + } + } + } + CHECK(!bad_mutexes_held); + } + // Don't record monitors as they are outside the scope of analysis. They may be inspected off of + // the monitor list. + if (level_ != kMonitorLock) { + self->SetHeldMutex(level_, this); + } +} + +inline void BaseMutex::RegisterAsUnlocked(Thread* self) { + if (UNLIKELY(self == NULL)) { + CheckUnattachedThread(level_); + return; + } + if (level_ != kMonitorLock) { + if (kDebugLocking && !gAborting) { + CHECK(self->GetHeldMutex(level_) == this) << "Unlocking on unacquired mutex: " << name_; + } + self->SetHeldMutex(level_, NULL); + } +} + +inline void ReaderWriterMutex::SharedLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (LIKELY(cur_state >= 0)) { + // Add as an extra reader. + done = android_atomic_acquire_cas(cur_state, cur_state + 1, &state_) == 0; + } else { + // Owner holds it exclusively, hang up. + ScopedContentionRecorder scr(this, GetExclusiveOwnerTid(), SafeGetTid(self)); + android_atomic_inc(&num_pending_readers_); + if (futex(&state_, FUTEX_WAIT, cur_state, NULL, NULL, 0) != 0) { + if (errno != EAGAIN) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + android_atomic_dec(&num_pending_readers_); + } + } while (!done); +#else + CHECK_MUTEX_CALL(pthread_rwlock_rdlock, (&rwlock_)); +#endif + RegisterAsLocked(self); + AssertSharedHeld(self); +} + +inline void ReaderWriterMutex::SharedUnlock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertSharedHeld(self); + RegisterAsUnlocked(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (LIKELY(cur_state > 0)) { + // Reduce state by 1. + done = android_atomic_release_cas(cur_state, cur_state - 1, &state_) == 0; + if (done && (cur_state - 1) == 0) { // cas may fail due to noise? + if (num_pending_writers_ > 0 || num_pending_readers_ > 0) { + // Wake any exclusive waiters as there are now no readers. + futex(&state_, FUTEX_WAKE, -1, NULL, NULL, 0); + } + } + } else { + LOG(FATAL) << "Unexpected state_:" << cur_state << " for " << name_; + } + } while (!done); +#else + CHECK_MUTEX_CALL(pthread_rwlock_unlock, (&rwlock_)); +#endif +} + +} // namespace art + +#endif // ART_RUNTIME_BASE_MUTEX_INL_H_ diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc new file mode 100644 index 0000000..b99e7c9 --- /dev/null +++ b/runtime/base/mutex.cc @@ -0,0 +1,920 @@ +/* + * Copyright (C) 2011 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 "mutex.h" + +#include <errno.h> +#include <sys/time.h> + +#include "atomic.h" +#include "base/logging.h" +#include "cutils/atomic.h" +#include "cutils/atomic-inline.h" +#include "mutex-inl.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" +#include "thread-inl.h" +#include "utils.h" + +namespace art { + +#if defined(__APPLE__) + +// This works on Mac OS 10.6 but hasn't been tested on older releases. +struct __attribute__((__may_alias__)) darwin_pthread_mutex_t { + long padding0; // NOLINT(runtime/int) exact match to darwin type + int padding1; + uint32_t padding2; + int16_t padding3; + int16_t padding4; + uint32_t padding5; + pthread_t darwin_pthread_mutex_owner; + // ...other stuff we don't care about. +}; + +struct __attribute__((__may_alias__)) darwin_pthread_rwlock_t { + long padding0; // NOLINT(runtime/int) exact match to darwin type + pthread_mutex_t padding1; + int padding2; + pthread_cond_t padding3; + pthread_cond_t padding4; + int padding5; + int padding6; + pthread_t darwin_pthread_rwlock_owner; + // ...other stuff we don't care about. +}; + +#endif // __APPLE__ + +#if defined(__GLIBC__) + +struct __attribute__((__may_alias__)) glibc_pthread_mutex_t { + int32_t padding0[2]; + int owner; + // ...other stuff we don't care about. +}; + +struct __attribute__((__may_alias__)) glibc_pthread_rwlock_t { +#ifdef __LP64__ + int32_t padding0[6]; +#else + int32_t padding0[7]; +#endif + int writer; + // ...other stuff we don't care about. +}; + +#endif // __GLIBC__ + +#if ART_USE_FUTEXES +static bool ComputeRelativeTimeSpec(timespec* result_ts, const timespec& lhs, const timespec& rhs) { + const int32_t one_sec = 1000 * 1000 * 1000; // one second in nanoseconds. + result_ts->tv_sec = lhs.tv_sec - rhs.tv_sec; + result_ts->tv_nsec = lhs.tv_nsec - rhs.tv_nsec; + if (result_ts->tv_nsec < 0) { + result_ts->tv_sec--; + result_ts->tv_nsec += one_sec; + } else if (result_ts->tv_nsec > one_sec) { + result_ts->tv_sec++; + result_ts->tv_nsec -= one_sec; + } + return result_ts->tv_sec < 0; +} +#endif + +struct AllMutexData { + // A guard for all_mutexes_ that's not a mutex (Mutexes must CAS to acquire and busy wait). + AtomicInteger all_mutexes_guard; + // All created mutexes guarded by all_mutexes_guard_. + std::set<BaseMutex*>* all_mutexes; + AllMutexData() : all_mutexes(NULL) {} +}; +static struct AllMutexData all_mutex_data[kAllMutexDataSize]; + +class ScopedAllMutexesLock { + public: + explicit ScopedAllMutexesLock(const BaseMutex* mutex) : mutex_(mutex) { + while (!all_mutex_data->all_mutexes_guard.compare_and_swap(0, reinterpret_cast<int32_t>(mutex))) { + NanoSleep(100); + } + } + ~ScopedAllMutexesLock() { + while (!all_mutex_data->all_mutexes_guard.compare_and_swap(reinterpret_cast<int32_t>(mutex_), 0)) { + NanoSleep(100); + } + } + private: + const BaseMutex* const mutex_; +}; + +BaseMutex::BaseMutex(const char* name, LockLevel level) : level_(level), name_(name) { + if (kLogLockContentions) { + ScopedAllMutexesLock mu(this); + std::set<BaseMutex*>** all_mutexes_ptr = &all_mutex_data->all_mutexes; + if (*all_mutexes_ptr == NULL) { + // We leak the global set of all mutexes to avoid ordering issues in global variable + // construction/destruction. + *all_mutexes_ptr = new std::set<BaseMutex*>(); + } + (*all_mutexes_ptr)->insert(this); + } +} + +BaseMutex::~BaseMutex() { + if (kLogLockContentions) { + ScopedAllMutexesLock mu(this); + all_mutex_data->all_mutexes->erase(this); + } +} + +void BaseMutex::DumpAll(std::ostream& os) { + if (kLogLockContentions) { + os << "Mutex logging:\n"; + ScopedAllMutexesLock mu(reinterpret_cast<const BaseMutex*>(-1)); + std::set<BaseMutex*>* all_mutexes = all_mutex_data->all_mutexes; + if (all_mutexes == NULL) { + // No mutexes have been created yet during at startup. + return; + } + typedef std::set<BaseMutex*>::const_iterator It; + os << "(Contented)\n"; + for (It it = all_mutexes->begin(); it != all_mutexes->end(); ++it) { + BaseMutex* mutex = *it; + if (mutex->HasEverContended()) { + mutex->Dump(os); + os << "\n"; + } + } + os << "(Never contented)\n"; + for (It it = all_mutexes->begin(); it != all_mutexes->end(); ++it) { + BaseMutex* mutex = *it; + if (!mutex->HasEverContended()) { + mutex->Dump(os); + os << "\n"; + } + } + } +} + +void BaseMutex::CheckSafeToWait(Thread* self) { + if (self == NULL) { + CheckUnattachedThread(level_); + return; + } + if (kDebugLocking) { + CHECK(self->GetHeldMutex(level_) == this) << "Waiting on unacquired mutex: " << name_; + bool bad_mutexes_held = false; + for (int i = kLockLevelCount - 1; i >= 0; --i) { + if (i != level_) { + BaseMutex* held_mutex = self->GetHeldMutex(static_cast<LockLevel>(i)); + if (held_mutex != NULL) { + LOG(ERROR) << "Holding \"" << held_mutex->name_ << "\" " + << "(level " << LockLevel(i) << ") while performing wait on " + << "\"" << name_ << "\" (level " << level_ << ")"; + bad_mutexes_held = true; + } + } + } + CHECK(!bad_mutexes_held); + } +} + +inline void BaseMutex::ContentionLogData::AddToWaitTime(uint64_t value) { + if (kLogLockContentions) { + // Atomically add value to wait_time. + uint64_t new_val, old_val; + volatile int64_t* addr = reinterpret_cast<volatile int64_t*>(&wait_time); + volatile const int64_t* caddr = const_cast<volatile const int64_t*>(addr); + do { + old_val = static_cast<uint64_t>(QuasiAtomic::Read64(caddr)); + new_val = old_val + value; + } while (!QuasiAtomic::Cas64(static_cast<int64_t>(old_val), static_cast<int64_t>(new_val), addr)); + } +} + +void BaseMutex::RecordContention(uint64_t blocked_tid, + uint64_t owner_tid, + uint64_t nano_time_blocked) { + if (kLogLockContentions) { + ContentionLogData* data = contetion_log_data_; + ++(data->contention_count); + data->AddToWaitTime(nano_time_blocked); + ContentionLogEntry* log = data->contention_log; + // This code is intentionally racy as it is only used for diagnostics. + uint32_t slot = data->cur_content_log_entry; + if (log[slot].blocked_tid == blocked_tid && + log[slot].owner_tid == blocked_tid) { + ++log[slot].count; + } else { + uint32_t new_slot; + do { + slot = data->cur_content_log_entry; + new_slot = (slot + 1) % kContentionLogSize; + } while (!data->cur_content_log_entry.compare_and_swap(slot, new_slot)); + log[new_slot].blocked_tid = blocked_tid; + log[new_slot].owner_tid = owner_tid; + log[new_slot].count = 1; + } + } +} + +void BaseMutex::DumpContention(std::ostream& os) const { + if (kLogLockContentions) { + const ContentionLogData* data = contetion_log_data_; + const ContentionLogEntry* log = data->contention_log; + uint64_t wait_time = data->wait_time; + uint32_t contention_count = data->contention_count; + if (contention_count == 0) { + os << "never contended"; + } else { + os << "contended " << contention_count + << " times, average wait of contender " << PrettyDuration(wait_time / contention_count); + SafeMap<uint64_t, size_t> most_common_blocker; + SafeMap<uint64_t, size_t> most_common_blocked; + typedef SafeMap<uint64_t, size_t>::const_iterator It; + for (size_t i = 0; i < kContentionLogSize; ++i) { + uint64_t blocked_tid = log[i].blocked_tid; + uint64_t owner_tid = log[i].owner_tid; + uint32_t count = log[i].count; + if (count > 0) { + It it = most_common_blocked.find(blocked_tid); + if (it != most_common_blocked.end()) { + most_common_blocked.Overwrite(blocked_tid, it->second + count); + } else { + most_common_blocked.Put(blocked_tid, count); + } + it = most_common_blocker.find(owner_tid); + if (it != most_common_blocker.end()) { + most_common_blocker.Overwrite(owner_tid, it->second + count); + } else { + most_common_blocker.Put(owner_tid, count); + } + } + } + uint64_t max_tid = 0; + size_t max_tid_count = 0; + for (It it = most_common_blocked.begin(); it != most_common_blocked.end(); ++it) { + if (it->second > max_tid_count) { + max_tid = it->first; + max_tid_count = it->second; + } + } + if (max_tid != 0) { + os << " sample shows most blocked tid=" << max_tid; + } + max_tid = 0; + max_tid_count = 0; + for (It it = most_common_blocker.begin(); it != most_common_blocker.end(); ++it) { + if (it->second > max_tid_count) { + max_tid = it->first; + max_tid_count = it->second; + } + } + if (max_tid != 0) { + os << " sample shows tid=" << max_tid << " owning during this time"; + } + } + } +} + + +Mutex::Mutex(const char* name, LockLevel level, bool recursive) + : BaseMutex(name, level), recursive_(recursive), recursion_count_(0) { +#if ART_USE_FUTEXES + state_ = 0; + exclusive_owner_ = 0; + num_contenders_ = 0; +#elif defined(__BIONIC__) || defined(__APPLE__) + // Use recursive mutexes for bionic and Apple otherwise the + // non-recursive mutexes don't have TIDs to check lock ownership of. + pthread_mutexattr_t attributes; + CHECK_MUTEX_CALL(pthread_mutexattr_init, (&attributes)); + CHECK_MUTEX_CALL(pthread_mutexattr_settype, (&attributes, PTHREAD_MUTEX_RECURSIVE)); + CHECK_MUTEX_CALL(pthread_mutex_init, (&mutex_, &attributes)); + CHECK_MUTEX_CALL(pthread_mutexattr_destroy, (&attributes)); +#else + CHECK_MUTEX_CALL(pthread_mutex_init, (&mutex_, NULL)); +#endif +} + +Mutex::~Mutex() { +#if ART_USE_FUTEXES + if (state_ != 0) { + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + LOG(shutting_down ? WARNING : FATAL) << "destroying mutex with owner: " << exclusive_owner_; + } else { + CHECK_EQ(exclusive_owner_, 0U) << "unexpectedly found an owner on unlocked mutex " << name_; + CHECK_EQ(num_contenders_, 0) << "unexpectedly found a contender on mutex " << name_; + } +#else + // We can't use CHECK_MUTEX_CALL here because on shutdown a suspended daemon thread + // may still be using locks. + int rc = pthread_mutex_destroy(&mutex_); + if (rc != 0) { + errno = rc; + // TODO: should we just not log at all if shutting down? this could be the logging mutex! + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + PLOG(shutting_down ? WARNING : FATAL) << "pthread_mutex_destroy failed for " << name_; + } +#endif +} + +void Mutex::ExclusiveLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + if (kDebugLocking && !recursive_) { + AssertNotHeld(self); + } + if (!recursive_ || !IsExclusiveHeld(self)) { +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to 1. + done = android_atomic_acquire_cas(0, 1, &state_) == 0; + } else { + // Failed to acquire, hang up. + ScopedContentionRecorder scr(this, SafeGetTid(self), GetExclusiveOwnerTid()); + android_atomic_inc(&num_contenders_); + if (futex(&state_, FUTEX_WAIT, 1, NULL, NULL, 0) != 0) { + // EAGAIN and EINTR both indicate a spurious failure, try again from the beginning. + // We don't use TEMP_FAILURE_RETRY so we can intentionally retry to acquire the lock. + if ((errno != EAGAIN) && (errno != EINTR)) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + android_atomic_dec(&num_contenders_); + } + } while (!done); + DCHECK_EQ(state_, 1); + exclusive_owner_ = SafeGetTid(self); +#else + CHECK_MUTEX_CALL(pthread_mutex_lock, (&mutex_)); +#endif + RegisterAsLocked(self); + } + recursion_count_++; + if (kDebugLocking) { + CHECK(recursion_count_ == 1 || recursive_) << "Unexpected recursion count on mutex: " + << name_ << " " << recursion_count_; + AssertHeld(self); + } +} + +bool Mutex::ExclusiveTryLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + if (kDebugLocking && !recursive_) { + AssertNotHeld(self); + } + if (!recursive_ || !IsExclusiveHeld(self)) { +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to 1. + done = android_atomic_acquire_cas(0, 1, &state_) == 0; + } else { + return false; + } + } while (!done); + DCHECK_EQ(state_, 1); + exclusive_owner_ = SafeGetTid(self); +#else + int result = pthread_mutex_trylock(&mutex_); + if (result == EBUSY) { + return false; + } + if (result != 0) { + errno = result; + PLOG(FATAL) << "pthread_mutex_trylock failed for " << name_; + } +#endif + RegisterAsLocked(self); + } + recursion_count_++; + if (kDebugLocking) { + CHECK(recursion_count_ == 1 || recursive_) << "Unexpected recursion count on mutex: " + << name_ << " " << recursion_count_; + AssertHeld(self); + } + return true; +} + +void Mutex::ExclusiveUnlock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertHeld(self); + recursion_count_--; + if (!recursive_ || recursion_count_ == 0) { + if (kDebugLocking) { + CHECK(recursion_count_ == 0 || recursive_) << "Unexpected recursion count on mutex: " + << name_ << " " << recursion_count_; + } + RegisterAsUnlocked(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 1) { + // We're no longer the owner. + exclusive_owner_ = 0; + // Change state to 0. + done = android_atomic_release_cas(cur_state, 0, &state_) == 0; + if (done) { // Spurious fail? + // Wake a contender + if (num_contenders_ > 0) { + futex(&state_, FUTEX_WAKE, 1, NULL, NULL, 0); + } + } + } else { + // Logging acquires the logging lock, avoid infinite recursion in that case. + if (this != Locks::logging_lock_) { + LOG(FATAL) << "Unexpected state_ in unlock " << cur_state << " for " << name_; + } else { + LogMessageData data(__FILE__, __LINE__, INTERNAL_FATAL, -1); + LogMessage::LogLine(data, StringPrintf("Unexpected state_ %d in unlock for %s", + cur_state, name_).c_str()); + _exit(1); + } + } + } while (!done); +#else + CHECK_MUTEX_CALL(pthread_mutex_unlock, (&mutex_)); +#endif + } +} + +bool Mutex::IsExclusiveHeld(const Thread* self) const { + DCHECK(self == NULL || self == Thread::Current()); + bool result = (GetExclusiveOwnerTid() == SafeGetTid(self)); + if (kDebugLocking) { + // Sanity debug check that if we think it is locked we have it in our held mutexes. + if (result && self != NULL && level_ != kMonitorLock && !gAborting) { + CHECK_EQ(self->GetHeldMutex(level_), this); + } + } + return result; +} + +uint64_t Mutex::GetExclusiveOwnerTid() const { +#if ART_USE_FUTEXES + return exclusive_owner_; +#elif defined(__BIONIC__) + return static_cast<uint64_t>((mutex_.value >> 16) & 0xffff); +#elif defined(__GLIBC__) + return reinterpret_cast<const glibc_pthread_mutex_t*>(&mutex_)->owner; +#elif defined(__APPLE__) + const darwin_pthread_mutex_t* dpmutex = reinterpret_cast<const darwin_pthread_mutex_t*>(&mutex_); + pthread_t owner = dpmutex->darwin_pthread_mutex_owner; + // 0 for unowned, -1 for PTHREAD_MTX_TID_SWITCHING + // TODO: should we make darwin_pthread_mutex_owner volatile and recheck until not -1? + if ((owner == (pthread_t)0) || (owner == (pthread_t)-1)) { + return 0; + } + uint64_t tid; + CHECK_PTHREAD_CALL(pthread_threadid_np, (owner, &tid), __FUNCTION__); // Requires Mac OS 10.6 + return tid; +#else +#error unsupported C library +#endif +} + +void Mutex::Dump(std::ostream& os) const { + os << (recursive_ ? "recursive " : "non-recursive ") + << name_ + << " level=" << static_cast<int>(level_) + << " rec=" << recursion_count_ + << " owner=" << GetExclusiveOwnerTid() << " "; + DumpContention(os); +} + +std::ostream& operator<<(std::ostream& os, const Mutex& mu) { + mu.Dump(os); + return os; +} + +ReaderWriterMutex::ReaderWriterMutex(const char* name, LockLevel level) + : BaseMutex(name, level) +#if ART_USE_FUTEXES + , state_(0), exclusive_owner_(0), num_pending_readers_(0), num_pending_writers_(0) +#endif +{ // NOLINT(whitespace/braces) +#if !ART_USE_FUTEXES + CHECK_MUTEX_CALL(pthread_rwlock_init, (&rwlock_, NULL)); +#endif +} + +ReaderWriterMutex::~ReaderWriterMutex() { +#if ART_USE_FUTEXES + CHECK_EQ(state_, 0); + CHECK_EQ(exclusive_owner_, 0U); + CHECK_EQ(num_pending_readers_, 0); + CHECK_EQ(num_pending_writers_, 0); +#else + // We can't use CHECK_MUTEX_CALL here because on shutdown a suspended daemon thread + // may still be using locks. + int rc = pthread_rwlock_destroy(&rwlock_); + if (rc != 0) { + errno = rc; + // TODO: should we just not log at all if shutting down? this could be the logging mutex! + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = runtime == NULL || runtime->IsShuttingDown(); + PLOG(shutting_down ? WARNING : FATAL) << "pthread_rwlock_destroy failed for " << name_; + } +#endif +} + +void ReaderWriterMutex::ExclusiveLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertNotExclusiveHeld(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to -1. + done = android_atomic_acquire_cas(0, -1, &state_) == 0; + } else { + // Failed to acquire, hang up. + ScopedContentionRecorder scr(this, SafeGetTid(self), GetExclusiveOwnerTid()); + android_atomic_inc(&num_pending_writers_); + if (futex(&state_, FUTEX_WAIT, cur_state, NULL, NULL, 0) != 0) { + // EAGAIN and EINTR both indicate a spurious failure, try again from the beginning. + // We don't use TEMP_FAILURE_RETRY so we can intentionally retry to acquire the lock. + if ((errno != EAGAIN) && (errno != EINTR)) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + android_atomic_dec(&num_pending_writers_); + } + } while (!done); + DCHECK_EQ(state_, -1); + exclusive_owner_ = SafeGetTid(self); +#else + CHECK_MUTEX_CALL(pthread_rwlock_wrlock, (&rwlock_)); +#endif + RegisterAsLocked(self); + AssertExclusiveHeld(self); +} + +void ReaderWriterMutex::ExclusiveUnlock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + AssertExclusiveHeld(self); + RegisterAsUnlocked(self); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state == -1) { + // We're no longer the owner. + exclusive_owner_ = 0; + // Change state from -1 to 0. + done = android_atomic_release_cas(-1, 0, &state_) == 0; + if (done) { // cmpxchg may fail due to noise? + // Wake any waiters. + if (num_pending_readers_ > 0 || num_pending_writers_ > 0) { + futex(&state_, FUTEX_WAKE, -1, NULL, NULL, 0); + } + } + } else { + LOG(FATAL) << "Unexpected state_:" << cur_state << " for " << name_; + } + } while (!done); +#else + CHECK_MUTEX_CALL(pthread_rwlock_unlock, (&rwlock_)); +#endif +} + +#if HAVE_TIMED_RWLOCK +bool ReaderWriterMutex::ExclusiveLockWithTimeout(Thread* self, int64_t ms, int32_t ns) { + DCHECK(self == NULL || self == Thread::Current()); +#if ART_USE_FUTEXES + bool done = false; + timespec end_abs_ts; + InitTimeSpec(true, CLOCK_REALTIME, ms, ns, &end_abs_ts); + do { + int32_t cur_state = state_; + if (cur_state == 0) { + // Change state from 0 to -1. + done = android_atomic_acquire_cas(0, -1, &state_) == 0; + } else { + // Failed to acquire, hang up. + timespec now_abs_ts; + InitTimeSpec(true, CLOCK_REALTIME, 0, 0, &now_abs_ts); + timespec rel_ts; + if (ComputeRelativeTimeSpec(&rel_ts, end_abs_ts, now_abs_ts)) { + return false; // Timed out. + } + ScopedContentionRecorder scr(this, SafeGetTid(self), GetExclusiveOwnerTid()); + android_atomic_inc(&num_pending_writers_); + if (futex(&state_, FUTEX_WAIT, cur_state, &rel_ts, NULL, 0) != 0) { + if (errno == ETIMEDOUT) { + android_atomic_dec(&num_pending_writers_); + return false; // Timed out. + } else if ((errno != EAGAIN) && (errno != EINTR)) { + // EAGAIN and EINTR both indicate a spurious failure, + // recompute the relative time out from now and try again. + // We don't use TEMP_FAILURE_RETRY so we can recompute rel_ts; + PLOG(FATAL) << "timed futex wait failed for " << name_; + } + } + android_atomic_dec(&num_pending_writers_); + } + } while (!done); + exclusive_owner_ = SafeGetTid(self); +#else + timespec ts; + InitTimeSpec(true, CLOCK_REALTIME, ms, ns, &ts); + int result = pthread_rwlock_timedwrlock(&rwlock_, &ts); + if (result == ETIMEDOUT) { + return false; + } + if (result != 0) { + errno = result; + PLOG(FATAL) << "pthread_rwlock_timedwrlock failed for " << name_; + } +#endif + RegisterAsLocked(self); + AssertSharedHeld(self); + return true; +} +#endif + +bool ReaderWriterMutex::SharedTryLock(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); +#if ART_USE_FUTEXES + bool done = false; + do { + int32_t cur_state = state_; + if (cur_state >= 0) { + // Add as an extra reader. + done = android_atomic_acquire_cas(cur_state, cur_state + 1, &state_) == 0; + } else { + // Owner holds it exclusively. + return false; + } + } while (!done); +#else + int result = pthread_rwlock_tryrdlock(&rwlock_); + if (result == EBUSY) { + return false; + } + if (result != 0) { + errno = result; + PLOG(FATAL) << "pthread_mutex_trylock failed for " << name_; + } +#endif + RegisterAsLocked(self); + AssertSharedHeld(self); + return true; +} + +bool ReaderWriterMutex::IsExclusiveHeld(const Thread* self) const { + DCHECK(self == NULL || self == Thread::Current()); + bool result = (GetExclusiveOwnerTid() == SafeGetTid(self)); + if (kDebugLocking) { + // Sanity that if the pthread thinks we own the lock the Thread agrees. + if (self != NULL && result) { + CHECK_EQ(self->GetHeldMutex(level_), this); + } + } + return result; +} + +bool ReaderWriterMutex::IsSharedHeld(const Thread* self) const { + DCHECK(self == NULL || self == Thread::Current()); + bool result; + if (UNLIKELY(self == NULL)) { // Handle unattached threads. + result = IsExclusiveHeld(self); // TODO: a better best effort here. + } else { + result = (self->GetHeldMutex(level_) == this); + } + return result; +} + +uint64_t ReaderWriterMutex::GetExclusiveOwnerTid() const { +#if ART_USE_FUTEXES + int32_t state = state_; + if (state == 0) { + return 0; // No owner. + } else if (state > 0) { + return -1; // Shared. + } else { + return exclusive_owner_; + } +#else +#if defined(__BIONIC__) + return rwlock_.writerThreadId; +#elif defined(__GLIBC__) + return reinterpret_cast<const glibc_pthread_rwlock_t*>(&rwlock_)->writer; +#elif defined(__APPLE__) + const darwin_pthread_rwlock_t* + dprwlock = reinterpret_cast<const darwin_pthread_rwlock_t*>(&rwlock_); + pthread_t owner = dprwlock->darwin_pthread_rwlock_owner; + if (owner == (pthread_t)0) { + return 0; + } + uint64_t tid; + CHECK_PTHREAD_CALL(pthread_threadid_np, (owner, &tid), __FUNCTION__); // Requires Mac OS 10.6 + return tid; +#else +#error unsupported C library +#endif +#endif +} + +void ReaderWriterMutex::Dump(std::ostream& os) const { + os << name_ + << " level=" << static_cast<int>(level_) + << " owner=" << GetExclusiveOwnerTid() << " "; + DumpContention(os); +} + +std::ostream& operator<<(std::ostream& os, const ReaderWriterMutex& mu) { + mu.Dump(os); + return os; +} + +ConditionVariable::ConditionVariable(const char* name, Mutex& guard) + : name_(name), guard_(guard) { +#if ART_USE_FUTEXES + sequence_ = 0; + num_waiters_ = 0; +#else + CHECK_MUTEX_CALL(pthread_cond_init, (&cond_, NULL)); +#endif +} + +ConditionVariable::~ConditionVariable() { +#if ART_USE_FUTEXES + if (num_waiters_!= 0) { + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + LOG(shutting_down ? WARNING : FATAL) << "ConditionVariable::~ConditionVariable for " << name_ + << " called with " << num_waiters_ << " waiters."; + } +#else + // We can't use CHECK_MUTEX_CALL here because on shutdown a suspended daemon thread + // may still be using condition variables. + int rc = pthread_cond_destroy(&cond_); + if (rc != 0) { + errno = rc; + MutexLock mu(Thread::Current(), *Locks::runtime_shutdown_lock_); + Runtime* runtime = Runtime::Current(); + bool shutting_down = (runtime == NULL) || runtime->IsShuttingDown(); + PLOG(shutting_down ? WARNING : FATAL) << "pthread_cond_destroy failed for " << name_; + } +#endif +} + +void ConditionVariable::Broadcast(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + // TODO: enable below, there's a race in thread creation that causes false failures currently. + // guard_.AssertExclusiveHeld(self); + DCHECK_EQ(guard_.GetExclusiveOwnerTid(), SafeGetTid(self)); +#if ART_USE_FUTEXES + if (num_waiters_ > 0) { + android_atomic_inc(&sequence_); // Indicate the broadcast occurred. + bool done = false; + do { + int32_t cur_sequence = sequence_; + // Requeue waiters onto mutex. The waiter holds the contender count on the mutex high ensuring + // mutex unlocks will awaken the requeued waiter thread. + done = futex(&sequence_, FUTEX_CMP_REQUEUE, 0, + reinterpret_cast<const timespec*>(std::numeric_limits<int32_t>::max()), + &guard_.state_, cur_sequence) != -1; + if (!done) { + if (errno != EAGAIN) { + PLOG(FATAL) << "futex cmp requeue failed for " << name_; + } + } + } while (!done); + } +#else + CHECK_MUTEX_CALL(pthread_cond_broadcast, (&cond_)); +#endif +} + +void ConditionVariable::Signal(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + guard_.AssertExclusiveHeld(self); +#if ART_USE_FUTEXES + if (num_waiters_ > 0) { + android_atomic_inc(&sequence_); // Indicate a signal occurred. + // Futex wake 1 waiter who will then come and in contend on mutex. It'd be nice to requeue them + // to avoid this, however, requeueing can only move all waiters. + int num_woken = futex(&sequence_, FUTEX_WAKE, 1, NULL, NULL, 0); + // Check something was woken or else we changed sequence_ before they had chance to wait. + CHECK((num_woken == 0) || (num_woken == 1)); + } +#else + CHECK_MUTEX_CALL(pthread_cond_signal, (&cond_)); +#endif +} + +void ConditionVariable::Wait(Thread* self) { + guard_.CheckSafeToWait(self); + WaitHoldingLocks(self); +} + +void ConditionVariable::WaitHoldingLocks(Thread* self) { + DCHECK(self == NULL || self == Thread::Current()); + guard_.AssertExclusiveHeld(self); + unsigned int old_recursion_count = guard_.recursion_count_; +#if ART_USE_FUTEXES + num_waiters_++; + // Ensure the Mutex is contended so that requeued threads are awoken. + android_atomic_inc(&guard_.num_contenders_); + guard_.recursion_count_ = 1; + int32_t cur_sequence = sequence_; + guard_.ExclusiveUnlock(self); + if (futex(&sequence_, FUTEX_WAIT, cur_sequence, NULL, NULL, 0) != 0) { + // Futex failed, check it is an expected error. + // EAGAIN == EWOULDBLK, so we let the caller try again. + // EINTR implies a signal was sent to this thread. + if ((errno != EINTR) && (errno != EAGAIN)) { + PLOG(FATAL) << "futex wait failed for " << name_; + } + } + guard_.ExclusiveLock(self); + CHECK_GE(num_waiters_, 0); + num_waiters_--; + // We awoke and so no longer require awakes from the guard_'s unlock. + CHECK_GE(guard_.num_contenders_, 0); + android_atomic_dec(&guard_.num_contenders_); +#else + guard_.recursion_count_ = 0; + CHECK_MUTEX_CALL(pthread_cond_wait, (&cond_, &guard_.mutex_)); +#endif + guard_.recursion_count_ = old_recursion_count; +} + +void ConditionVariable::TimedWait(Thread* self, int64_t ms, int32_t ns) { + DCHECK(self == NULL || self == Thread::Current()); + guard_.AssertExclusiveHeld(self); + guard_.CheckSafeToWait(self); + unsigned int old_recursion_count = guard_.recursion_count_; +#if ART_USE_FUTEXES + timespec rel_ts; + InitTimeSpec(false, CLOCK_REALTIME, ms, ns, &rel_ts); + num_waiters_++; + // Ensure the Mutex is contended so that requeued threads are awoken. + android_atomic_inc(&guard_.num_contenders_); + guard_.recursion_count_ = 1; + int32_t cur_sequence = sequence_; + guard_.ExclusiveUnlock(self); + if (futex(&sequence_, FUTEX_WAIT, cur_sequence, &rel_ts, NULL, 0) != 0) { + if (errno == ETIMEDOUT) { + // Timed out we're done. + } else if ((errno == EAGAIN) || (errno == EINTR)) { + // A signal or ConditionVariable::Signal/Broadcast has come in. + } else { + PLOG(FATAL) << "timed futex wait failed for " << name_; + } + } + guard_.ExclusiveLock(self); + CHECK_GE(num_waiters_, 0); + num_waiters_--; + // We awoke and so no longer require awakes from the guard_'s unlock. + CHECK_GE(guard_.num_contenders_, 0); + android_atomic_dec(&guard_.num_contenders_); +#else +#ifdef HAVE_TIMEDWAIT_MONOTONIC +#define TIMEDWAIT pthread_cond_timedwait_monotonic + int clock = CLOCK_MONOTONIC; +#else +#define TIMEDWAIT pthread_cond_timedwait + int clock = CLOCK_REALTIME; +#endif + guard_.recursion_count_ = 0; + timespec ts; + InitTimeSpec(true, clock, ms, ns, &ts); + int rc = TEMP_FAILURE_RETRY(TIMEDWAIT(&cond_, &guard_.mutex_, &ts)); + if (rc != 0 && rc != ETIMEDOUT) { + errno = rc; + PLOG(FATAL) << "TimedWait failed for " << name_; + } +#endif + guard_.recursion_count_ = old_recursion_count; +} + +} // namespace art diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h new file mode 100644 index 0000000..ee37388 --- /dev/null +++ b/runtime/base/mutex.h @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2011 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_RUNTIME_BASE_MUTEX_H_ +#define ART_RUNTIME_BASE_MUTEX_H_ + +#include <pthread.h> +#include <stdint.h> + +#include <iosfwd> +#include <string> + +#include "atomic_integer.h" +#include "base/logging.h" +#include "base/macros.h" +#include "globals.h" +#include "locks.h" + +#if defined(__APPLE__) +#define ART_USE_FUTEXES 0 +#else +#define ART_USE_FUTEXES !defined(__mips__) +#endif + +// Currently Darwin doesn't support locks with timeouts. +#if !defined(__APPLE__) +#define HAVE_TIMED_RWLOCK 1 +#else +#define HAVE_TIMED_RWLOCK 0 +#endif + +namespace art { + +class ScopedContentionRecorder; +class Thread; + +const bool kDebugLocking = kIsDebugBuild; + +// Record Log contention information, dumpable via SIGQUIT. +#ifdef ART_USE_FUTEXES +// To enable lock contention logging, set this to true. +const bool kLogLockContentions = false; +#else +// Keep this false as lock contention logging is supported only with +// futex. +const bool kLogLockContentions = false; +#endif +const size_t kContentionLogSize = 64; +const size_t kContentionLogDataSize = kLogLockContentions ? 1 : 0; +const size_t kAllMutexDataSize = kLogLockContentions ? 1 : 0; + +// Base class for all Mutex implementations +class BaseMutex { + public: + const char* GetName() const { + return name_; + } + + virtual bool IsMutex() const { return false; } + virtual bool IsReaderWriterMutex() const { return false; } + + virtual void Dump(std::ostream& os) const = 0; + + static void DumpAll(std::ostream& os); + + protected: + friend class ConditionVariable; + + BaseMutex(const char* name, LockLevel level); + virtual ~BaseMutex(); + void RegisterAsLocked(Thread* self); + void RegisterAsUnlocked(Thread* self); + void CheckSafeToWait(Thread* self); + + friend class ScopedContentionRecorder; + + void RecordContention(uint64_t blocked_tid, uint64_t owner_tid, uint64_t nano_time_blocked); + void DumpContention(std::ostream& os) const; + + const LockLevel level_; // Support for lock hierarchy. + const char* const name_; + + // A log entry that records contention but makes no guarantee that either tid will be held live. + struct ContentionLogEntry { + ContentionLogEntry() : blocked_tid(0), owner_tid(0) {} + uint64_t blocked_tid; + uint64_t owner_tid; + AtomicInteger count; + }; + struct ContentionLogData { + ContentionLogEntry contention_log[kContentionLogSize]; + // The next entry in the contention log to be updated. Value ranges from 0 to + // kContentionLogSize - 1. + AtomicInteger cur_content_log_entry; + // Number of times the Mutex has been contended. + AtomicInteger contention_count; + // Sum of time waited by all contenders in ns. + volatile uint64_t wait_time; + void AddToWaitTime(uint64_t value); + ContentionLogData() : wait_time(0) {} + }; + ContentionLogData contetion_log_data_[kContentionLogDataSize]; + + public: + bool HasEverContended() const { + if (kLogLockContentions) { + return contetion_log_data_->contention_count > 0; + } + return false; + } +}; + +// A Mutex is used to achieve mutual exclusion between threads. A Mutex can be used to gain +// exclusive access to what it guards. A Mutex can be in one of two states: +// - Free - not owned by any thread, +// - Exclusive - owned by a single thread. +// +// The effect of locking and unlocking operations on the state is: +// State | ExclusiveLock | ExclusiveUnlock +// ------------------------------------------- +// Free | Exclusive | error +// Exclusive | Block* | Free +// * Mutex is not reentrant and so an attempt to ExclusiveLock on the same thread will result in +// an error. Being non-reentrant simplifies Waiting on ConditionVariables. +std::ostream& operator<<(std::ostream& os, const Mutex& mu); +class LOCKABLE Mutex : public BaseMutex { + public: + explicit Mutex(const char* name, LockLevel level = kDefaultMutexLevel, bool recursive = false); + ~Mutex(); + + virtual bool IsMutex() const { return true; } + + // Block until mutex is free then acquire exclusive access. + void ExclusiveLock(Thread* self) EXCLUSIVE_LOCK_FUNCTION(); + void Lock(Thread* self) EXCLUSIVE_LOCK_FUNCTION() { ExclusiveLock(self); } + + // Returns true if acquires exclusive access, false otherwise. + bool ExclusiveTryLock(Thread* self) EXCLUSIVE_TRYLOCK_FUNCTION(true); + bool TryLock(Thread* self) EXCLUSIVE_TRYLOCK_FUNCTION(true) { return ExclusiveTryLock(self); } + + // Release exclusive access. + void ExclusiveUnlock(Thread* self) UNLOCK_FUNCTION(); + void Unlock(Thread* self) UNLOCK_FUNCTION() { ExclusiveUnlock(self); } + + // Is the current thread the exclusive holder of the Mutex. + bool IsExclusiveHeld(const Thread* self) const; + + // Assert that the Mutex is exclusively held by the current thread. + void AssertExclusiveHeld(const Thread* self) { + if (kDebugLocking && (gAborting == 0)) { + CHECK(IsExclusiveHeld(self)) << *this; + } + } + void AssertHeld(const Thread* self) { AssertExclusiveHeld(self); } + + // Assert that the Mutex is not held by the current thread. + void AssertNotHeldExclusive(const Thread* self) { + if (kDebugLocking && (gAborting == 0)) { + CHECK(!IsExclusiveHeld(self)) << *this; + } + } + void AssertNotHeld(const Thread* self) { AssertNotHeldExclusive(self); } + + // Id associated with exclusive owner. + uint64_t GetExclusiveOwnerTid() const; + + // Returns how many times this Mutex has been locked, it is better to use AssertHeld/NotHeld. + unsigned int GetDepth() const { + return recursion_count_; + } + + virtual void Dump(std::ostream& os) const; + + private: +#if ART_USE_FUTEXES + // 0 is unheld, 1 is held. + volatile int32_t state_; + // Exclusive owner. + volatile uint64_t exclusive_owner_; + // Number of waiting contenders. + volatile int32_t num_contenders_; +#else + pthread_mutex_t mutex_; +#endif + const bool recursive_; // Can the lock be recursively held? + unsigned int recursion_count_; + friend class ConditionVariable; + DISALLOW_COPY_AND_ASSIGN(Mutex); +}; + +// A ReaderWriterMutex is used to achieve mutual exclusion between threads, similar to a Mutex. +// Unlike a Mutex a ReaderWriterMutex can be used to gain exclusive (writer) or shared (reader) +// access to what it guards. A flaw in relation to a Mutex is that it cannot be used with a +// condition variable. A ReaderWriterMutex can be in one of three states: +// - Free - not owned by any thread, +// - Exclusive - owned by a single thread, +// - Shared(n) - shared amongst n threads. +// +// The effect of locking and unlocking operations on the state is: +// +// State | ExclusiveLock | ExclusiveUnlock | SharedLock | SharedUnlock +// ---------------------------------------------------------------------------- +// Free | Exclusive | error | SharedLock(1) | error +// Exclusive | Block | Free | Block | error +// Shared(n) | Block | error | SharedLock(n+1)* | Shared(n-1) or Free +// * for large values of n the SharedLock may block. +std::ostream& operator<<(std::ostream& os, const ReaderWriterMutex& mu); +class LOCKABLE ReaderWriterMutex : public BaseMutex { + public: + explicit ReaderWriterMutex(const char* name, LockLevel level = kDefaultMutexLevel); + ~ReaderWriterMutex(); + + virtual bool IsReaderWriterMutex() const { return true; } + + // Block until ReaderWriterMutex is free then acquire exclusive access. + void ExclusiveLock(Thread* self) EXCLUSIVE_LOCK_FUNCTION(); + void WriterLock(Thread* self) EXCLUSIVE_LOCK_FUNCTION() { ExclusiveLock(self); } + + // Release exclusive access. + void ExclusiveUnlock(Thread* self) UNLOCK_FUNCTION(); + void WriterUnlock(Thread* self) UNLOCK_FUNCTION() { ExclusiveUnlock(self); } + + // Block until ReaderWriterMutex is free and acquire exclusive access. Returns true on success + // or false if timeout is reached. +#if HAVE_TIMED_RWLOCK + bool ExclusiveLockWithTimeout(Thread* self, int64_t ms, int32_t ns) + EXCLUSIVE_TRYLOCK_FUNCTION(true); +#endif + + // Block until ReaderWriterMutex is shared or free then acquire a share on the access. + void SharedLock(Thread* self) SHARED_LOCK_FUNCTION() ALWAYS_INLINE; + void ReaderLock(Thread* self) SHARED_LOCK_FUNCTION() { SharedLock(self); } + + // Try to acquire share of ReaderWriterMutex. + bool SharedTryLock(Thread* self) EXCLUSIVE_TRYLOCK_FUNCTION(true); + + // Release a share of the access. + void SharedUnlock(Thread* self) UNLOCK_FUNCTION() ALWAYS_INLINE; + void ReaderUnlock(Thread* self) UNLOCK_FUNCTION() { SharedUnlock(self); } + + // Is the current thread the exclusive holder of the ReaderWriterMutex. + bool IsExclusiveHeld(const Thread* self) const; + + // Assert the current thread has exclusive access to the ReaderWriterMutex. + void AssertExclusiveHeld(const Thread* self) { + if (kDebugLocking && (gAborting == 0)) { + CHECK(IsExclusiveHeld(self)) << *this; + } + } + void AssertWriterHeld(const Thread* self) { AssertExclusiveHeld(self); } + + // Assert the current thread doesn't have exclusive access to the ReaderWriterMutex. + void AssertNotExclusiveHeld(const Thread* self) { + if (kDebugLocking && (gAborting == 0)) { + CHECK(!IsExclusiveHeld(self)) << *this; + } + } + void AssertNotWriterHeld(const Thread* self) { AssertNotExclusiveHeld(self); } + + // Is the current thread a shared holder of the ReaderWriterMutex. + bool IsSharedHeld(const Thread* self) const; + + // Assert the current thread has shared access to the ReaderWriterMutex. + void AssertSharedHeld(const Thread* self) { + if (kDebugLocking && (gAborting == 0)) { + // TODO: we can only assert this well when self != NULL. + CHECK(IsSharedHeld(self) || self == NULL) << *this; + } + } + void AssertReaderHeld(const Thread* self) { AssertSharedHeld(self); } + + // Assert the current thread doesn't hold this ReaderWriterMutex either in shared or exclusive + // mode. + void AssertNotHeld(const Thread* self) { + if (kDebugLocking && (gAborting == 0)) { + CHECK(!IsSharedHeld(self)) << *this; + } + } + + // Id associated with exclusive owner. + uint64_t GetExclusiveOwnerTid() const; + + virtual void Dump(std::ostream& os) const; + + private: +#if ART_USE_FUTEXES + // -1 implies held exclusive, +ve shared held by state_ many owners. + volatile int32_t state_; + // Exclusive owner. + volatile uint64_t exclusive_owner_; + // Pending readers. + volatile int32_t num_pending_readers_; + // Pending writers. + volatile int32_t num_pending_writers_; +#else + pthread_rwlock_t rwlock_; +#endif + DISALLOW_COPY_AND_ASSIGN(ReaderWriterMutex); +}; + +// ConditionVariables allow threads to queue and sleep. Threads may then be resumed individually +// (Signal) or all at once (Broadcast). +class ConditionVariable { + public: + explicit ConditionVariable(const char* name, Mutex& mutex); + ~ConditionVariable(); + + void Broadcast(Thread* self); + void Signal(Thread* self); + // TODO: No thread safety analysis on Wait and TimedWait as they call mutex operations via their + // pointer copy, thereby defeating annotalysis. + void Wait(Thread* self) NO_THREAD_SAFETY_ANALYSIS; + void TimedWait(Thread* self, int64_t ms, int32_t ns) NO_THREAD_SAFETY_ANALYSIS; + // Variant of Wait that should be used with caution. Doesn't validate that no mutexes are held + // when waiting. + // TODO: remove this. + void WaitHoldingLocks(Thread* self) NO_THREAD_SAFETY_ANALYSIS; + + private: + const char* const name_; + // The Mutex being used by waiters. It is an error to mix condition variables between different + // Mutexes. + Mutex& guard_; +#if ART_USE_FUTEXES + // A counter that is modified by signals and broadcasts. This ensures that when a waiter gives up + // their Mutex and another thread takes it and signals, the waiting thread observes that sequence_ + // changed and doesn't enter the wait. Modified while holding guard_, but is read by futex wait + // without guard_ held. + volatile int32_t sequence_; + // Number of threads that have come into to wait, not the length of the waiters on the futex as + // waiters may have been requeued onto guard_. Guarded by guard_. + volatile int32_t num_waiters_; +#else + pthread_cond_t cond_; +#endif + DISALLOW_COPY_AND_ASSIGN(ConditionVariable); +}; + +// Scoped locker/unlocker for a regular Mutex that acquires mu upon construction and releases it +// upon destruction. +class SCOPED_LOCKABLE MutexLock { + public: + explicit MutexLock(Thread* self, Mutex& mu) EXCLUSIVE_LOCK_FUNCTION(mu) : self_(self), mu_(mu) { + mu_.ExclusiveLock(self_); + } + + ~MutexLock() UNLOCK_FUNCTION() { + mu_.ExclusiveUnlock(self_); + } + + private: + Thread* const self_; + Mutex& mu_; + DISALLOW_COPY_AND_ASSIGN(MutexLock); +}; +// Catch bug where variable name is omitted. "MutexLock (lock);" instead of "MutexLock mu(lock)". +#define MutexLock(x) COMPILE_ASSERT(0, mutex_lock_declaration_missing_variable_name) + +// Scoped locker/unlocker for a ReaderWriterMutex that acquires read access to mu upon +// construction and releases it upon destruction. +class SCOPED_LOCKABLE ReaderMutexLock { + public: + explicit ReaderMutexLock(Thread* self, ReaderWriterMutex& mu) EXCLUSIVE_LOCK_FUNCTION(mu) : + self_(self), mu_(mu) { + mu_.SharedLock(self_); + } + + ~ReaderMutexLock() UNLOCK_FUNCTION() { + mu_.SharedUnlock(self_); + } + + private: + Thread* const self_; + ReaderWriterMutex& mu_; + DISALLOW_COPY_AND_ASSIGN(ReaderMutexLock); +}; +// Catch bug where variable name is omitted. "ReaderMutexLock (lock);" instead of +// "ReaderMutexLock mu(lock)". +#define ReaderMutexLock(x) COMPILE_ASSERT(0, reader_mutex_lock_declaration_missing_variable_name) + +// Scoped locker/unlocker for a ReaderWriterMutex that acquires write access to mu upon +// construction and releases it upon destruction. +class SCOPED_LOCKABLE WriterMutexLock { + public: + explicit WriterMutexLock(Thread* self, ReaderWriterMutex& mu) EXCLUSIVE_LOCK_FUNCTION(mu) : + self_(self), mu_(mu) { + mu_.ExclusiveLock(self_); + } + + ~WriterMutexLock() UNLOCK_FUNCTION() { + mu_.ExclusiveUnlock(self_); + } + + private: + Thread* const self_; + ReaderWriterMutex& mu_; + DISALLOW_COPY_AND_ASSIGN(WriterMutexLock); +}; +// Catch bug where variable name is omitted. "WriterMutexLock (lock);" instead of +// "WriterMutexLock mu(lock)". +#define WriterMutexLock(x) COMPILE_ASSERT(0, writer_mutex_lock_declaration_missing_variable_name) + +} // namespace art + +#endif // ART_RUNTIME_BASE_MUTEX_H_ diff --git a/runtime/base/mutex_test.cc b/runtime/base/mutex_test.cc new file mode 100644 index 0000000..1af8e0a --- /dev/null +++ b/runtime/base/mutex_test.cc @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2012 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 "mutex.h" + +#include "common_test.h" + +namespace art { + +class MutexTest : public CommonTest {}; + +struct MutexTester { + static void AssertDepth(Mutex& mu, uint32_t expected_depth) { + ASSERT_EQ(expected_depth, mu.GetDepth()); + + // This test is single-threaded, so we also know _who_ should hold the lock. + if (expected_depth == 0) { + mu.AssertNotHeld(Thread::Current()); + } else { + mu.AssertHeld(Thread::Current()); + } + } +}; + +TEST_F(MutexTest, LockUnlock) { + Mutex mu("test mutex"); + MutexTester::AssertDepth(mu, 0U); + mu.Lock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void TryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + Mutex mu("test mutex"); + MutexTester::AssertDepth(mu, 0U); + ASSERT_TRUE(mu.TryLock(Thread::Current())); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +TEST_F(MutexTest, TryLockUnlock) { + TryLockUnlockTest(); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void RecursiveLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + Mutex mu("test mutex", kDefaultMutexLevel, true); + MutexTester::AssertDepth(mu, 0U); + mu.Lock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Lock(Thread::Current()); + MutexTester::AssertDepth(mu, 2U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +TEST_F(MutexTest, RecursiveLockUnlock) { + RecursiveLockUnlockTest(); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void RecursiveTryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + Mutex mu("test mutex", kDefaultMutexLevel, true); + MutexTester::AssertDepth(mu, 0U); + ASSERT_TRUE(mu.TryLock(Thread::Current())); + MutexTester::AssertDepth(mu, 1U); + ASSERT_TRUE(mu.TryLock(Thread::Current())); + MutexTester::AssertDepth(mu, 2U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 1U); + mu.Unlock(Thread::Current()); + MutexTester::AssertDepth(mu, 0U); +} + +TEST_F(MutexTest, RecursiveTryLockUnlock) { + RecursiveTryLockUnlockTest(); +} + + +struct RecursiveLockWait { + explicit RecursiveLockWait() + : mu("test mutex", kDefaultMutexLevel, true), cv("test condition variable", mu) { + } + + static void* Callback(void* arg) { + RecursiveLockWait* state = reinterpret_cast<RecursiveLockWait*>(arg); + state->mu.Lock(Thread::Current()); + state->cv.Signal(Thread::Current()); + state->mu.Unlock(Thread::Current()); + return NULL; + } + + Mutex mu; + ConditionVariable cv; +}; + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void RecursiveLockWaitTest() NO_THREAD_SAFETY_ANALYSIS { + RecursiveLockWait state; + state.mu.Lock(Thread::Current()); + state.mu.Lock(Thread::Current()); + + pthread_t pthread; + int pthread_create_result = pthread_create(&pthread, NULL, RecursiveLockWait::Callback, &state); + ASSERT_EQ(0, pthread_create_result); + + state.cv.Wait(Thread::Current()); + + state.mu.Unlock(Thread::Current()); + state.mu.Unlock(Thread::Current()); + EXPECT_EQ(pthread_join(pthread, NULL), 0); +} + +// This ensures we don't hang when waiting on a recursively locked mutex, +// which is not supported with bare pthread_mutex_t. +TEST_F(MutexTest, RecursiveLockWait) { + RecursiveLockWaitTest(); +} + +TEST_F(MutexTest, SharedLockUnlock) { + ReaderWriterMutex mu("test rwmutex"); + mu.AssertNotHeld(Thread::Current()); + mu.AssertNotExclusiveHeld(Thread::Current()); + mu.SharedLock(Thread::Current()); + mu.AssertSharedHeld(Thread::Current()); + mu.AssertNotExclusiveHeld(Thread::Current()); + mu.SharedUnlock(Thread::Current()); + mu.AssertNotHeld(Thread::Current()); +} + +TEST_F(MutexTest, ExclusiveLockUnlock) { + ReaderWriterMutex mu("test rwmutex"); + mu.AssertNotHeld(Thread::Current()); + mu.ExclusiveLock(Thread::Current()); + mu.AssertSharedHeld(Thread::Current()); + mu.AssertExclusiveHeld(Thread::Current()); + mu.ExclusiveUnlock(Thread::Current()); + mu.AssertNotHeld(Thread::Current()); +} + +// GCC has trouble with our mutex tests, so we have to turn off thread safety analysis. +static void SharedTryLockUnlockTest() NO_THREAD_SAFETY_ANALYSIS { + ReaderWriterMutex mu("test rwmutex"); + mu.AssertNotHeld(Thread::Current()); + ASSERT_TRUE(mu.SharedTryLock(Thread::Current())); + mu.AssertSharedHeld(Thread::Current()); + mu.SharedUnlock(Thread::Current()); + mu.AssertNotHeld(Thread::Current()); +} + +TEST_F(MutexTest, SharedTryLockUnlock) { + SharedTryLockUnlockTest(); +} + +} // namespace art diff --git a/runtime/base/stl_util.h b/runtime/base/stl_util.h new file mode 100644 index 0000000..ff9f40c --- /dev/null +++ b/runtime/base/stl_util.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 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_RUNTIME_BASE_STL_UTIL_H_ +#define ART_RUNTIME_BASE_STL_UTIL_H_ + +#include <algorithm> +#include <sstream> + +namespace art { + +// Sort and remove duplicates of an STL vector or deque. +template<class T> +void STLSortAndRemoveDuplicates(T* v) { + std::sort(v->begin(), v->end()); + v->erase(std::unique(v->begin(), v->end()), v->end()); +} + +// STLDeleteContainerPointers() +// For a range within a container of pointers, calls delete +// (non-array version) on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template <class ForwardIterator> +void STLDeleteContainerPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// STLDeleteElements() deletes all the elements in an STL container and clears +// the container. This function is suitable for use with a vector, set, +// hash_set, or any other STL container which defines sensible begin(), end(), +// and clear() methods. +// +// If container is NULL, this function is a no-op. +// +// As an alternative to calling STLDeleteElements() directly, consider +// ElementDeleter (defined below), which ensures that your container's elements +// are deleted when the ElementDeleter goes out of scope. +template <class T> +void STLDeleteElements(T *container) { + if (!container) return; + STLDeleteContainerPointers(container->begin(), container->end()); + container->clear(); +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template <class T> +void STLDeleteValues(T *v) { + if (!v) return; + for (typename T::iterator i = v->begin(); i != v->end(); ++i) { + delete i->second; + } + v->clear(); +} + +template <class T> +std::string ToString(const T& v) { + std::ostringstream os; + os << "["; + for (size_t i = 0; i < v.size(); ++i) { + os << v[i]; + if (i < v.size() - 1) { + os << ", "; + } + } + os << "]"; + return os.str(); +} + +} // namespace art + +#endif // ART_RUNTIME_BASE_STL_UTIL_H_ diff --git a/runtime/base/stringpiece.cc b/runtime/base/stringpiece.cc new file mode 100644 index 0000000..47140e3 --- /dev/null +++ b/runtime/base/stringpiece.cc @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010 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 "stringpiece.h" + +#include <iostream> +#include <utility> + +namespace art { + +void StringPiece::CopyToString(std::string* target) const { + target->assign(ptr_, length_); +} + +int StringPiece::copy(char* buf, size_type n, size_type pos) const { + int ret = std::min(length_ - pos, n); + memcpy(buf, ptr_ + pos, ret); + return ret; +} + +StringPiece::size_type StringPiece::find(const StringPiece& s, size_type pos) const { + if (length_ < 0 || pos > static_cast<size_type>(length_)) + return npos; + + const char* result = std::search(ptr_ + pos, ptr_ + length_, + s.ptr_, s.ptr_ + s.length_); + const size_type xpos = result - ptr_; + return xpos + s.length_ <= static_cast<size_type>(length_) ? xpos : npos; +} + +int StringPiece::compare(const StringPiece& x) const { + int r = memcmp(ptr_, x.ptr_, std::min(length_, x.length_)); + if (r == 0) { + if (length_ < x.length_) r = -1; + else if (length_ > x.length_) r = +1; + } + return r; +} + +StringPiece::size_type StringPiece::find(char c, size_type pos) const { + if (length_ <= 0 || pos >= static_cast<size_type>(length_)) { + return npos; + } + const char* result = std::find(ptr_ + pos, ptr_ + length_, c); + return result != ptr_ + length_ ? result - ptr_ : npos; +} + +StringPiece::size_type StringPiece::rfind(const StringPiece& s, size_type pos) const { + if (length_ < s.length_) return npos; + const size_t ulen = length_; + if (s.length_ == 0) return std::min(ulen, pos); + + const char* last = ptr_ + std::min(ulen - s.length_, pos) + s.length_; + const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_); + return result != last ? result - ptr_ : npos; +} + +StringPiece::size_type StringPiece::rfind(char c, size_type pos) const { + if (length_ <= 0) return npos; + for (int i = std::min(pos, static_cast<size_type>(length_ - 1)); + i >= 0; --i) { + if (ptr_[i] == c) { + return i; + } + } + return npos; +} + +StringPiece StringPiece::substr(size_type pos, size_type n) const { + if (pos > static_cast<size_type>(length_)) pos = length_; + if (n > length_ - pos) n = length_ - pos; + return StringPiece(ptr_ + pos, n); +} + +const StringPiece::size_type StringPiece::npos = size_type(-1); + +std::ostream& operator<<(std::ostream& o, const StringPiece& piece) { + o.write(piece.data(), piece.size()); + return o; +} + +} // namespace art diff --git a/runtime/base/stringpiece.h b/runtime/base/stringpiece.h new file mode 100644 index 0000000..91b83f6 --- /dev/null +++ b/runtime/base/stringpiece.h @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2010 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. + */ + +// A string-like object that points to a sized piece of memory. +// +// Functions or methods may use const StringPiece& parameters to accept either +// a "const char*" or a "string" value that will be implicitly converted to +// a StringPiece. The implicit conversion means that it is often appropriate +// to include this .h file in other files rather than forward-declaring +// StringPiece as would be appropriate for most other Google classes. +// +// Systematic usage of StringPiece is encouraged as it will reduce unnecessary +// conversions from "const char*" to "string" and back again. + +#ifndef ART_RUNTIME_BASE_STRINGPIECE_H_ +#define ART_RUNTIME_BASE_STRINGPIECE_H_ + +#include <string.h> +#include <algorithm> +#include <cstddef> +#include <iosfwd> +#include <string> + +namespace art { + +class StringPiece { + private: + const char* ptr_; + int length_; + + public: + // We provide non-explicit singleton constructors so users can pass + // in a "const char*" or a "string" wherever a "StringPiece" is + // expected. + StringPiece() : ptr_(NULL), length_(0) { } + StringPiece(const char* str) // NOLINT + : ptr_(str), length_((str == NULL) ? 0 : static_cast<int>(strlen(str))) { } + StringPiece(const std::string& str) // NOLINT + : ptr_(str.data()), length_(static_cast<int>(str.size())) { } + StringPiece(const char* offset, int len) : ptr_(offset), length_(len) { } + + // data() may return a pointer to a buffer with embedded NULs, and the + // returned buffer may or may not be null terminated. Therefore it is + // typically a mistake to pass data() to a routine that expects a NUL + // terminated string. + const char* data() const { return ptr_; } + int size() const { return length_; } + int length() const { return length_; } + bool empty() const { return length_ == 0; } + + void clear() { + ptr_ = NULL; + length_ = 0; + } + void set(const char* data, int len) { + ptr_ = data; + length_ = len; + } + void set(const char* str) { + ptr_ = str; + if (str != NULL) + length_ = static_cast<int>(strlen(str)); + else + length_ = 0; + } + void set(const void* data, int len) { + ptr_ = reinterpret_cast<const char*>(data); + length_ = len; + } + + char operator[](int i) const { return ptr_[i]; } + + void remove_prefix(int n) { + ptr_ += n; + length_ -= n; + } + + void remove_suffix(int n) { + length_ -= n; + } + + int compare(const StringPiece& x) const; + + std::string as_string() const { + return std::string(data(), size()); + } + // We also define ToString() here, since many other string-like + // interfaces name the routine that converts to a C++ string + // "ToString", and it's confusing to have the method that does that + // for a StringPiece be called "as_string()". We also leave the + // "as_string()" method defined here for existing code. + std::string ToString() const { + return std::string(data(), size()); + } + + void CopyToString(std::string* target) const; + void AppendToString(std::string* target) const; + + // Does "this" start with "x" + bool starts_with(const StringPiece& x) const { + return ((length_ >= x.length_) && + (memcmp(ptr_, x.ptr_, x.length_) == 0)); + } + + // Does "this" end with "x" + bool ends_with(const StringPiece& x) const { + return ((length_ >= x.length_) && + (memcmp(ptr_ + (length_-x.length_), x.ptr_, x.length_) == 0)); + } + + // standard STL container boilerplate + typedef char value_type; + typedef const char* pointer; + typedef const char& reference; + typedef const char& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + static const size_type npos; + typedef const char* const_iterator; + typedef const char* iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + typedef std::reverse_iterator<iterator> reverse_iterator; + iterator begin() const { return ptr_; } + iterator end() const { return ptr_ + length_; } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(ptr_ + length_); + } + const_reverse_iterator rend() const { + return const_reverse_iterator(ptr_); + } + // STLS says return size_type, but Google says return int + int max_size() const { return length_; } + int capacity() const { return length_; } + + int copy(char* buf, size_type n, size_type pos = 0) const; + + size_type find(const StringPiece& s, size_type pos = 0) const; + size_type find(char c, size_type pos = 0) const; + size_type rfind(const StringPiece& s, size_type pos = npos) const; + size_type rfind(char c, size_type pos = npos) const; + + StringPiece substr(size_type pos, size_type n = npos) const; +}; + +// This large function is defined inline so that in a fairly common case where +// one of the arguments is a literal, the compiler can elide a lot of the +// following comparisons. +inline bool operator==(const StringPiece& x, const StringPiece& y) { + int len = x.size(); + if (len != y.size()) { + return false; + } + + const char* p1 = x.data(); + const char* p2 = y.data(); + if (p1 == p2) { + return true; + } + if (len <= 0) { + return true; + } + + // Test last byte in case strings share large common prefix + if (p1[len-1] != p2[len-1]) return false; + if (len == 1) return true; + + // At this point we can, but don't have to, ignore the last byte. We use + // this observation to fold the odd-length case into the even-length case. + len &= ~1; + + return memcmp(p1, p2, len) == 0; +} + +inline bool operator!=(const StringPiece& x, const StringPiece& y) { + return !(x == y); +} + +inline bool operator<(const StringPiece& x, const StringPiece& y) { + const int r = memcmp(x.data(), y.data(), + std::min(x.size(), y.size())); + return ((r < 0) || ((r == 0) && (x.size() < y.size()))); +} + +inline bool operator>(const StringPiece& x, const StringPiece& y) { + return y < x; +} + +inline bool operator<=(const StringPiece& x, const StringPiece& y) { + return !(x > y); +} + +inline bool operator>=(const StringPiece& x, const StringPiece& y) { + return !(x < y); +} + +extern std::ostream& operator<<(std::ostream& o, const StringPiece& piece); + +} // namespace art + +#endif // ART_RUNTIME_BASE_STRINGPIECE_H_ diff --git a/runtime/base/stringprintf.cc b/runtime/base/stringprintf.cc new file mode 100644 index 0000000..8fd9257 --- /dev/null +++ b/runtime/base/stringprintf.cc @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011 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 "stringprintf.h" + +#include <stdio.h> + +namespace art { + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + // First try with a small fixed size buffer + char space[1024]; + + // It's possible for methods that use a va_list to invalidate + // the data in it upon use. The fix is to make a copy + // of the structure before using it and use that copy instead. + va_list backup_ap; + va_copy(backup_ap, ap); + int result = vsnprintf(space, sizeof(space), format, backup_ap); + va_end(backup_ap); + + if (result < static_cast<int>(sizeof(space))) { + if (result >= 0) { + // Normal case -- everything fit. + dst->append(space, result); + return; + } + + if (result < 0) { + // Just an error. + return; + } + } + + // Increase the buffer size to the size requested by vsnprintf, + // plus one for the closing \0. + int length = result+1; + char* buf = new char[length]; + + // Restore the va_list before we use it again + va_copy(backup_ap, ap); + result = vsnprintf(buf, length, format, backup_ap); + va_end(backup_ap); + + if (result >= 0 && result < length) { + // It fit + dst->append(buf, result); + } + delete[] buf; +} + +std::string StringPrintf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string result; + StringAppendV(&result, fmt, ap); + va_end(ap); + return result; +} + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +} // namespace art diff --git a/runtime/base/stringprintf.h b/runtime/base/stringprintf.h new file mode 100644 index 0000000..4767a75 --- /dev/null +++ b/runtime/base/stringprintf.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 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_RUNTIME_BASE_STRINGPRINTF_H_ +#define ART_RUNTIME_BASE_STRINGPRINTF_H_ + +#include <stdarg.h> +#include <string> + +namespace art { + +// Returns a string corresponding to printf-like formatting of the arguments. +std::string StringPrintf(const char* fmt, ...) + __attribute__((__format__(__printf__, 1, 2))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendF(std::string* dst, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendV(std::string* dst, const char* format, va_list ap); + +} // namespace art + +#endif // ART_RUNTIME_BASE_STRINGPRINTF_H_ diff --git a/runtime/base/timing_logger.cc b/runtime/base/timing_logger.cc new file mode 100644 index 0000000..11dc542 --- /dev/null +++ b/runtime/base/timing_logger.cc @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2011 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. + */ + + +#define ATRACE_TAG ATRACE_TAG_DALVIK +#include <stdio.h> +#include <cutils/trace.h> + +#include "timing_logger.h" + +#include "base/logging.h" +#include "thread.h" +#include "base/stl_util.h" +#include "base/histogram-inl.h" + +#include <cmath> +#include <iomanip> + +namespace art { + +CumulativeLogger::CumulativeLogger(const std::string& name) + : name_(name), + lock_name_("CumulativeLoggerLock" + name), + lock_(lock_name_.c_str(), kDefaultMutexLevel, true) { + Reset(); +} + +CumulativeLogger::~CumulativeLogger() { + STLDeleteValues(&histograms_); +} + +void CumulativeLogger::SetName(const std::string& name) { + name_.assign(name); +} + +void CumulativeLogger::Start() { +} + +void CumulativeLogger::End() { + MutexLock mu(Thread::Current(), lock_); + iterations_++; +} + +void CumulativeLogger::Reset() { + MutexLock mu(Thread::Current(), lock_); + iterations_ = 0; + STLDeleteValues(&histograms_); +} + +uint64_t CumulativeLogger::GetTotalNs() const { + return GetTotalTime() * kAdjust; +} + +uint64_t CumulativeLogger::GetTotalTime() const { + MutexLock mu(Thread::Current(), lock_); + uint64_t total = 0; + for (CumulativeLogger::HistogramsIterator it = histograms_.begin(), end = histograms_.end(); + it != end; ++it) { + total += it->second->Sum(); + } + return total; +} + +void CumulativeLogger::AddLogger(const base::TimingLogger &logger) { + MutexLock mu(Thread::Current(), lock_); + const base::TimingLogger::SplitTimings& splits = logger.GetSplits(); + for (base::TimingLogger::SplitTimingsIterator it = splits.begin(), end = splits.end(); + it != end; ++it) { + base::TimingLogger::SplitTiming split = *it; + uint64_t split_time = split.first; + const char* split_name = split.second; + AddPair(split_name, split_time); + } +} + +void CumulativeLogger::Dump(std::ostream &os) { + MutexLock mu(Thread::Current(), lock_); + DumpHistogram(os); +} + +void CumulativeLogger::AddPair(const std::string &label, uint64_t delta_time) { + // Convert delta time to microseconds so that we don't overflow our counters. + delta_time /= kAdjust; + + if (histograms_.find(label) == histograms_.end()) { + // TODO: Shoud this be a defined constant so we we know out of which orifice 16 and 100 were picked? + const size_t max_buckets = Runtime::Current()->GetHeap()->IsLowMemoryMode() ? 16 : 100; + // TODO: Should this be a defined constant so we know 50 of WTF? + histograms_[label] = new Histogram<uint64_t>(label.c_str(), 50, max_buckets); + } + histograms_[label]->AddValue(delta_time); +} + +void CumulativeLogger::DumpHistogram(std::ostream &os) { + os << "Start Dumping histograms for " << iterations_ << " iterations" + << " for " << name_ << "\n"; + for (CumulativeLogger::HistogramsIterator it = histograms_.begin(), end = histograms_.end(); + it != end; ++it) { + Histogram<uint64_t>::CumulativeData cumulative_data; + it->second->CreateHistogram(cumulative_data); + it->second->PrintConfidenceIntervals(os, 0.99, cumulative_data); + // Reset cumulative values to save memory. We don't expect DumpHistogram to be called often, so + // it is not performance critical. + } + os << "Done Dumping histograms \n"; +} + + +namespace base { + +TimingLogger::TimingLogger(const char* name, bool precise, bool verbose) + : name_(name), precise_(precise), verbose_(verbose), current_split_(NULL) { +} + +void TimingLogger::Reset() { + current_split_ = NULL; + splits_.clear(); +} + +void TimingLogger::StartSplit(const char* new_split_label) { + DCHECK(new_split_label != NULL) << "Starting split (" << new_split_label << ") with null label."; + TimingLogger::ScopedSplit* explicit_scoped_split = new TimingLogger::ScopedSplit(new_split_label, this); + explicit_scoped_split->explicit_ = true; +} + +void TimingLogger::EndSplit() { + CHECK(current_split_ != NULL) << "Ending a non-existent split."; + DCHECK(current_split_->label_ != NULL); + DCHECK(current_split_->explicit_ == true) << "Explicitly ending scoped split: " << current_split_->label_; + + delete current_split_; +} + +// Ends the current split and starts the one given by the label. +void TimingLogger::NewSplit(const char* new_split_label) { + CHECK(current_split_ != NULL) << "Inserting a new split (" << new_split_label + << ") into a non-existent split."; + DCHECK(new_split_label != NULL) << "New split (" << new_split_label << ") with null label."; + + current_split_->TailInsertSplit(new_split_label); +} + +uint64_t TimingLogger::GetTotalNs() const { + uint64_t total_ns = 0; + for (base::TimingLogger::SplitTimingsIterator it = splits_.begin(), end = splits_.end(); + it != end; ++it) { + base::TimingLogger::SplitTiming split = *it; + total_ns += split.first; + } + return total_ns; +} + +void TimingLogger::Dump(std::ostream &os) const { + uint64_t longest_split = 0; + uint64_t total_ns = 0; + for (base::TimingLogger::SplitTimingsIterator it = splits_.begin(), end = splits_.end(); + it != end; ++it) { + base::TimingLogger::SplitTiming split = *it; + uint64_t split_time = split.first; + longest_split = std::max(longest_split, split_time); + total_ns += split_time; + } + // Compute which type of unit we will use for printing the timings. + TimeUnit tu = GetAppropriateTimeUnit(longest_split); + uint64_t divisor = GetNsToTimeUnitDivisor(tu); + // Print formatted splits. + for (base::TimingLogger::SplitTimingsIterator it = splits_.begin(), end = splits_.end(); + it != end; ++it) { + base::TimingLogger::SplitTiming split = *it; + uint64_t split_time = split.first; + if (!precise_ && divisor >= 1000) { + // Make the fractional part 0. + split_time -= split_time % (divisor / 1000); + } + os << name_ << ": " << std::setw(8) << FormatDuration(split_time, tu) << " " + << split.second << "\n"; + } + os << name_ << ": end, " << NsToMs(total_ns) << " ms\n"; +} + + +TimingLogger::ScopedSplit::ScopedSplit(const char* label, TimingLogger* timing_logger) { + DCHECK(label != NULL) << "New scoped split (" << label << ") with null label."; + CHECK(timing_logger != NULL) << "New scoped split (" << label << ") without TimingLogger."; + timing_logger_ = timing_logger; + label_ = label; + running_ns_ = 0; + explicit_ = false; + + // Stash away the current split and pause it. + enclosing_split_ = timing_logger->current_split_; + if (enclosing_split_ != NULL) { + enclosing_split_->Pause(); + } + + timing_logger_->current_split_ = this; + + ATRACE_BEGIN(label_); + + start_ns_ = NanoTime(); + if (timing_logger_->verbose_) { + LOG(INFO) << "Begin: " << label_; + } +} + +TimingLogger::ScopedSplit::~ScopedSplit() { + uint64_t current_time = NanoTime(); + uint64_t split_time = current_time - start_ns_; + running_ns_ += split_time; + ATRACE_END(); + + if (timing_logger_->verbose_) { + LOG(INFO) << "End: " << label_ << " " << PrettyDuration(split_time); + } + + // If one or more enclosed explcitly started splits are not terminated we can + // either fail or "unwind" the stack of splits in the timing logger to 'this' + // (by deleting the intervening scoped splits). This implements the latter. + TimingLogger::ScopedSplit* current = timing_logger_->current_split_; + while ((current != NULL) && (current != this)) { + delete current; + current = timing_logger_->current_split_; + } + + CHECK(current != NULL) << "Missing scoped split (" << this->label_ + << ") in timing logger (" << timing_logger_->name_ << ")."; + CHECK(timing_logger_->current_split_ == this); + + timing_logger_->splits_.push_back(SplitTiming(running_ns_, label_)); + + timing_logger_->current_split_ = enclosing_split_; + if (enclosing_split_ != NULL) { + enclosing_split_->Resume(); + } +} + + +void TimingLogger::ScopedSplit::TailInsertSplit(const char* label) { + // Sleight of hand here: Rather than embedding a new scoped split, we're updating the current + // scoped split in place. Basically, it's one way to make explicit and scoped splits compose + // well while maintaining the current semantics of NewSplit. An alternative is to push a new split + // since we unwind the stack of scoped splits in the scoped split destructor. However, this implies + // that the current split is not ended by NewSplit (which calls TailInsertSplit), which would + // be different from what we had before. + + uint64_t current_time = NanoTime(); + uint64_t split_time = current_time - start_ns_; + ATRACE_END(); + timing_logger_->splits_.push_back(std::pair<uint64_t, const char*>(split_time, label_)); + + if (timing_logger_->verbose_) { + LOG(INFO) << "End: " << label_ << " " << PrettyDuration(split_time) << "\n" + << "Begin: " << label; + } + + label_ = label; + start_ns_ = current_time; + running_ns_ = 0; + + ATRACE_BEGIN(label); +} + +void TimingLogger::ScopedSplit::Pause() { + uint64_t current_time = NanoTime(); + uint64_t split_time = current_time - start_ns_; + running_ns_ += split_time; + ATRACE_END(); +} + + +void TimingLogger::ScopedSplit::Resume() { + uint64_t current_time = NanoTime(); + + start_ns_ = current_time; + ATRACE_BEGIN(label_); +} + +} // namespace base +} // namespace art diff --git a/runtime/base/timing_logger.h b/runtime/base/timing_logger.h new file mode 100644 index 0000000..07d1ee0 --- /dev/null +++ b/runtime/base/timing_logger.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2011 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_RUNTIME_BASE_TIMING_LOGGER_H_ +#define ART_RUNTIME_BASE_TIMING_LOGGER_H_ + +#include "base/histogram.h" +#include "base/macros.h" +#include "base/mutex.h" + +#include <string> +#include <vector> +#include <map> + +namespace art { + +namespace base { + class TimingLogger; +} // namespace base + +class CumulativeLogger { + public: + explicit CumulativeLogger(const std::string& name); + void prepare_stats(); + ~CumulativeLogger(); + void Start(); + void End(); + void Reset(); + void Dump(std::ostream& os) LOCKS_EXCLUDED(lock_); + uint64_t GetTotalNs() const; + // Allow the name to be modified, particularly when the cumulative logger is a field within a + // parent class that is unable to determine the "name" of a sub-class. + void SetName(const std::string& name); + void AddLogger(const base::TimingLogger& logger) LOCKS_EXCLUDED(lock_); + + private: + typedef std::map<std::string, Histogram<uint64_t> *> Histograms; + typedef std::map<std::string, Histogram<uint64_t> *>::const_iterator HistogramsIterator; + + void AddPair(const std::string &label, uint64_t delta_time) + EXCLUSIVE_LOCKS_REQUIRED(lock_); + void DumpHistogram(std::ostream &os) EXCLUSIVE_LOCKS_REQUIRED(lock_); + uint64_t GetTotalTime() const; + static const uint64_t kAdjust = 1000; + Histograms histograms_ GUARDED_BY(lock_); + std::string name_; + const std::string lock_name_; + mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; + size_t iterations_ GUARDED_BY(lock_); + + DISALLOW_COPY_AND_ASSIGN(CumulativeLogger); +}; + +namespace base { + + +// A timing logger that knows when a split starts for the purposes of logging tools, like systrace. +class TimingLogger { + public: + // Splits are nanosecond times and split names. + typedef std::pair<uint64_t, const char*> SplitTiming; + typedef std::vector<SplitTiming> SplitTimings; + typedef std::vector<SplitTiming>::const_iterator SplitTimingsIterator; + + explicit TimingLogger(const char* name, bool precise, bool verbose); + + // Clears current splits and labels. + void Reset(); + + // Starts a split + void StartSplit(const char* new_split_label); + + // Ends the current split and starts the one given by the label. + void NewSplit(const char* new_split_label); + + // Ends the current split and records the end time. + void EndSplit(); + + uint64_t GetTotalNs() const; + + void Dump(std::ostream& os) const; + + // Scoped timing splits that can be nested and composed with the explicit split + // starts and ends. + class ScopedSplit { + public: + explicit ScopedSplit(const char* label, TimingLogger* timing_logger); + + ~ScopedSplit(); + + friend class TimingLogger; + + private: + // Pauses timing of the split, usually due to nesting of another split. + void Pause(); + + // Resumes timing of the split, usually because a nested split has ended. + void Resume(); + + // Used by new split to swap splits in place in a ScopedSplit instance. + void TailInsertSplit(const char* label); + + // The scoped split immediately enclosing this split. Essentially, we get a + // stack of nested splits through this field. + ScopedSplit* enclosing_split_; + + // Was this created via TimingLogger's StartSplit? + bool explicit_; + + // The split's name. + const char* label_; + + // The current split's latest start time. (It may have been paused and restarted.) + uint64_t start_ns_; + + // The running time, outside of pauses. + uint64_t running_ns_; + + // The timing logger holding this split. + TimingLogger* timing_logger_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSplit); + }; + + const SplitTimings& GetSplits() const { + return splits_; + } + + friend class ScopedSplit; + protected: + // The name of the timing logger. + const char* name_; + + // Do we want to print the exactly recorded split (true) or round down to the time unit being + // used (false). + const bool precise_; + + // Verbose logging. + const bool verbose_; + + // The current scoped split is also the 'top' of the stack of splits in progress. + ScopedSplit* current_split_; + + // Splits that have ended. + SplitTimings splits_; + + private: + DISALLOW_COPY_AND_ASSIGN(TimingLogger); +}; + +} // namespace base +} // namespace art + +#endif // ART_RUNTIME_BASE_TIMING_LOGGER_H_ diff --git a/runtime/base/timing_logger_test.cc b/runtime/base/timing_logger_test.cc new file mode 100644 index 0000000..8f28e48 --- /dev/null +++ b/runtime/base/timing_logger_test.cc @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2012 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 "timing_logger.h" + +#include "common_test.h" + +namespace art { + +class TimingLoggerTest : public CommonTest {}; + +// TODO: Negative test cases (improper pairing of EndSplit, etc.) + +TEST_F(TimingLoggerTest, StartEnd) { + const char* split1name = "First Split"; + base::TimingLogger timings("StartEnd", true, false); + + timings.StartSplit(split1name); + + timings.EndSplit(); // Ends split1. + + const base::TimingLogger::SplitTimings& splits = timings.GetSplits(); + + EXPECT_EQ(1U, splits.size()); + EXPECT_STREQ(splits[0].second, split1name); +} + + +TEST_F(TimingLoggerTest, StartNewEnd) { + const char* split1name = "First Split"; + const char* split2name = "Second Split"; + const char* split3name = "Third Split"; + base::TimingLogger timings("StartNewEnd", true, false); + + timings.StartSplit(split1name); + + timings.NewSplit(split2name); // Ends split1. + + timings.NewSplit(split3name); // Ends split2. + + timings.EndSplit(); // Ends split3. + + const base::TimingLogger::SplitTimings& splits = timings.GetSplits(); + + EXPECT_EQ(3U, splits.size()); + EXPECT_STREQ(splits[0].second, split1name); + EXPECT_STREQ(splits[1].second, split2name); + EXPECT_STREQ(splits[2].second, split3name); +} + +TEST_F(TimingLoggerTest, StartNewEndNested) { + const char* split1name = "First Split"; + const char* split2name = "Second Split"; + const char* split3name = "Third Split"; + const char* split4name = "Fourth Split"; + const char* split5name = "Fifth Split"; + base::TimingLogger timings("StartNewEndNested", true, false); + + timings.StartSplit(split1name); + + timings.NewSplit(split2name); // Ends split1. + + timings.StartSplit(split3name); + + timings.StartSplit(split4name); + + timings.NewSplit(split5name); // Ends split4. + + timings.EndSplit(); // Ends split5. + + timings.EndSplit(); // Ends split3. + + timings.EndSplit(); // Ends split2. + + const base::TimingLogger::SplitTimings& splits = timings.GetSplits(); + + EXPECT_EQ(5U, splits.size()); + EXPECT_STREQ(splits[0].second, split1name); + EXPECT_STREQ(splits[1].second, split4name); + EXPECT_STREQ(splits[2].second, split5name); + EXPECT_STREQ(splits[3].second, split3name); + EXPECT_STREQ(splits[4].second, split2name); +} + + +TEST_F(TimingLoggerTest, Scoped) { + const char* outersplit = "Outer Split"; + const char* innersplit1 = "Inner Split 1"; + const char* innerinnersplit1 = "Inner Inner Split 1"; + const char* innersplit2 = "Inner Split 2"; + base::TimingLogger timings("Scoped", true, false); + + { + base::TimingLogger::ScopedSplit outer(outersplit, &timings); + + { + base::TimingLogger::ScopedSplit inner1(innersplit1, &timings); + + { + base::TimingLogger::ScopedSplit innerinner1(innerinnersplit1, &timings); + } // Ends innerinnersplit1. + } // Ends innersplit1. + + { + base::TimingLogger::ScopedSplit inner2(innersplit2, &timings); + } // Ends innersplit2. + } // Ends outersplit. + + const base::TimingLogger::SplitTimings& splits = timings.GetSplits(); + + EXPECT_EQ(4U, splits.size()); + EXPECT_STREQ(splits[0].second, innerinnersplit1); + EXPECT_STREQ(splits[1].second, innersplit1); + EXPECT_STREQ(splits[2].second, innersplit2); + EXPECT_STREQ(splits[3].second, outersplit); +} + + +TEST_F(TimingLoggerTest, ScopedAndExplicit) { + const char* outersplit = "Outer Split"; + const char* innersplit = "Inner Split"; + const char* innerinnersplit1 = "Inner Inner Split 1"; + const char* innerinnersplit2 = "Inner Inner Split 2"; + base::TimingLogger timings("Scoped", true, false); + + timings.StartSplit(outersplit); + + { + base::TimingLogger::ScopedSplit inner(innersplit, &timings); + + timings.StartSplit(innerinnersplit1); + + timings.NewSplit(innerinnersplit2); // Ends innerinnersplit1. + } // Ends innerinnersplit2, then innersplit. + + timings.EndSplit(); // Ends outersplit. + + const base::TimingLogger::SplitTimings& splits = timings.GetSplits(); + + EXPECT_EQ(4U, splits.size()); + EXPECT_STREQ(splits[0].second, innerinnersplit1); + EXPECT_STREQ(splits[1].second, innerinnersplit2); + EXPECT_STREQ(splits[2].second, innersplit); + EXPECT_STREQ(splits[3].second, outersplit); +} + +} // namespace art diff --git a/runtime/base/unix_file/README b/runtime/base/unix_file/README new file mode 100644 index 0000000..e9aec22 --- /dev/null +++ b/runtime/base/unix_file/README @@ -0,0 +1,15 @@ +A simple C++ wrapper for Unix file I/O. + +This is intended to be lightweight and easy to use, similar to Java's +RandomAccessFile and related classes. The usual C++ idioms of RAII and "you +don't pay for what you don't use" apply. + +In particular, the basic RandomAccessFile interface is kept small and simple so +it's trivial to add new implementations. + +This code will not log, because it can't know whether that's appropriate in +your application. + +This code will, in general, return -errno on failure. If an operation consisted +of multiple sub-operations, it will return the errno corresponding to the most +relevant operation. diff --git a/runtime/base/unix_file/fd_file.cc b/runtime/base/unix_file/fd_file.cc new file mode 100644 index 0000000..36f8ba7 --- /dev/null +++ b/runtime/base/unix_file/fd_file.cc @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009 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 "base/logging.h" +#include "base/unix_file/fd_file.h" +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +namespace unix_file { + +FdFile::FdFile() : fd_(-1), auto_close_(true) { +} + +FdFile::FdFile(int fd) : fd_(fd), auto_close_(true) { +} + +FdFile::FdFile(int fd, const std::string& path) : fd_(fd), file_path_(path), auto_close_(true) { + CHECK_NE(0U, path.size()); +} + +FdFile::~FdFile() { + if (auto_close_ && fd_ != -1) { + Close(); + } +} + +void FdFile::DisableAutoClose() { + auto_close_ = false; +} + +bool FdFile::Open(const std::string& path, int flags) { + return Open(path, flags, 0640); +} + +bool FdFile::Open(const std::string& path, int flags, mode_t mode) { + CHECK_EQ(fd_, -1) << path; + fd_ = TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode)); + if (fd_ == -1) { + return false; + } + file_path_ = path; + return true; +} + +int FdFile::Close() { + int result = TEMP_FAILURE_RETRY(close(fd_)); + if (result == -1) { + return -errno; + } else { + fd_ = -1; + file_path_ = ""; + return 0; + } +} + +int FdFile::Flush() { + int rc = TEMP_FAILURE_RETRY(fdatasync(fd_)); + return (rc == -1) ? -errno : rc; +} + +int64_t FdFile::Read(char* buf, int64_t byte_count, int64_t offset) const { + int rc = TEMP_FAILURE_RETRY(pread64(fd_, buf, byte_count, offset)); + return (rc == -1) ? -errno : rc; +} + +int FdFile::SetLength(int64_t new_length) { + int rc = TEMP_FAILURE_RETRY(ftruncate64(fd_, new_length)); + return (rc == -1) ? -errno : rc; +} + +int64_t FdFile::GetLength() const { + struct stat s; + int rc = TEMP_FAILURE_RETRY(fstat(fd_, &s)); + return (rc == -1) ? -errno : s.st_size; +} + +int64_t FdFile::Write(const char* buf, int64_t byte_count, int64_t offset) { + int rc = TEMP_FAILURE_RETRY(pwrite64(fd_, buf, byte_count, offset)); + return (rc == -1) ? -errno : rc; +} + +int FdFile::Fd() const { + return fd_; +} + +bool FdFile::IsOpened() const { + return fd_ >= 0; +} + +std::string FdFile::GetPath() const { + return file_path_; +} + +bool FdFile::ReadFully(void* buffer, int64_t byte_count) { + char* ptr = static_cast<char*>(buffer); + while (byte_count > 0) { + int bytes_read = TEMP_FAILURE_RETRY(read(fd_, ptr, byte_count)); + if (bytes_read <= 0) { + return false; + } + byte_count -= bytes_read; // Reduce the number of remaining bytes. + ptr += bytes_read; // Move the buffer forward. + } + return true; +} + +bool FdFile::WriteFully(const void* buffer, int64_t byte_count) { + const char* ptr = static_cast<const char*>(buffer); + while (byte_count > 0) { + int bytes_read = TEMP_FAILURE_RETRY(write(fd_, ptr, byte_count)); + if (bytes_read < 0) { + return false; + } + byte_count -= bytes_read; // Reduce the number of remaining bytes. + ptr += bytes_read; // Move the buffer forward. + } + return true; +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/fd_file.h b/runtime/base/unix_file/fd_file.h new file mode 100644 index 0000000..79a0db9 --- /dev/null +++ b/runtime/base/unix_file/fd_file.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2009 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_RUNTIME_BASE_UNIX_FILE_FD_FILE_H_ +#define ART_RUNTIME_BASE_UNIX_FILE_FD_FILE_H_ + +#include <fcntl.h> +#include <string> +#include "base/unix_file/random_access_file.h" +#include "base/macros.h" + +namespace unix_file { + +// A RandomAccessFile implementation backed by a file descriptor. +// +// Not thread safe. +class FdFile : public RandomAccessFile { + public: + FdFile(); + // Creates an FdFile using the given file descriptor. Takes ownership of the + // file descriptor. (Use DisableAutoClose to retain ownership.) + explicit FdFile(int fd); + explicit FdFile(int fd, const std::string& path); + + // Destroys an FdFile, closing the file descriptor if Close hasn't already + // been called. (If you care about the return value of Close, call it + // yourself; this is meant to handle failure cases and read-only accesses. + // Note though that calling Close and checking its return value is still no + // guarantee that data actually made it to stable storage.) + virtual ~FdFile(); + + // Opens file 'file_path' using 'flags' and 'mode'. + bool Open(const std::string& file_path, int flags); + bool Open(const std::string& file_path, int flags, mode_t mode); + + // RandomAccessFile API. + virtual int Close(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + virtual int Flush(); + + // Bonus API. + int Fd() const; + bool IsOpened() const; + std::string GetPath() const; + void DisableAutoClose(); + bool ReadFully(void* buffer, int64_t byte_count); + bool WriteFully(const void* buffer, int64_t byte_count); + + private: + int fd_; + std::string file_path_; + bool auto_close_; + + DISALLOW_COPY_AND_ASSIGN(FdFile); +}; + +} // namespace unix_file + +#endif // ART_RUNTIME_BASE_UNIX_FILE_FD_FILE_H_ diff --git a/runtime/base/unix_file/fd_file_test.cc b/runtime/base/unix_file/fd_file_test.cc new file mode 100644 index 0000000..d620666 --- /dev/null +++ b/runtime/base/unix_file/fd_file_test.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 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 "base/unix_file/fd_file.h" +#include "base/unix_file/random_access_file_test.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class FdFileTest : public RandomAccessFileTest { + protected: + virtual RandomAccessFile* MakeTestFile() { + return new FdFile(fileno(tmpfile())); + } +}; + +TEST_F(FdFileTest, Read) { + TestRead(); +} + +TEST_F(FdFileTest, SetLength) { + TestSetLength(); +} + +TEST_F(FdFileTest, Write) { + TestWrite(); +} + +TEST_F(FdFileTest, UnopenedFile) { + FdFile file; + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); + EXPECT_TRUE(file.GetPath().empty()); +} + +TEST_F(FdFileTest, OpenClose) { + std::string good_path(GetTmpPath("some-file.txt")); + FdFile file; + ASSERT_TRUE(file.Open(good_path, O_CREAT | O_WRONLY)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); + EXPECT_EQ(0, file.Close()); + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); + EXPECT_TRUE(file.Open(good_path, O_RDONLY)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/mapped_file.cc b/runtime/base/unix_file/mapped_file.cc new file mode 100644 index 0000000..b63fdd3 --- /dev/null +++ b/runtime/base/unix_file/mapped_file.cc @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2008 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 "base/logging.h" +#include "base/unix_file/mapped_file.h" +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <algorithm> +#include <string> + +namespace unix_file { + +MappedFile::~MappedFile() { +} + +int MappedFile::Close() { + if (IsMapped()) { + Unmap(); + } + return FdFile::Close(); +} + +bool MappedFile::MapReadOnly() { + CHECK(IsOpened()); + CHECK(!IsMapped()); + struct stat st; + int result = TEMP_FAILURE_RETRY(fstat(Fd(), &st)); + if (result == -1) { + PLOG(WARNING) << "Failed to stat file '" << GetPath() << "'"; + return false; + } + file_size_ = st.st_size; + do { + mapped_file_ = mmap(NULL, file_size_, PROT_READ, MAP_PRIVATE, Fd(), 0); + } while (mapped_file_ == MAP_FAILED && errno == EINTR); + if (mapped_file_ == MAP_FAILED) { + PLOG(WARNING) << "Failed to mmap file '" << GetPath() << "' of size " + << file_size_ << " bytes to memory"; + return false; + } + map_mode_ = kMapReadOnly; + return true; +} + +bool MappedFile::MapReadWrite(int64_t file_size) { + CHECK(IsOpened()); + CHECK(!IsMapped()); + int result = TEMP_FAILURE_RETRY(ftruncate64(Fd(), file_size)); + if (result == -1) { + PLOG(ERROR) << "Failed to truncate file '" << GetPath() + << "' to size " << file_size; + return false; + } + file_size_ = file_size; + do { + mapped_file_ = + mmap(NULL, file_size_, PROT_READ | PROT_WRITE, MAP_SHARED, Fd(), 0); + } while (mapped_file_ == MAP_FAILED && errno == EINTR); + if (mapped_file_ == MAP_FAILED) { + PLOG(WARNING) << "Failed to mmap file '" << GetPath() << "' of size " + << file_size_ << " bytes to memory"; + return false; + } + map_mode_ = kMapReadWrite; + return true; +} + +bool MappedFile::Unmap() { + CHECK(IsMapped()); + int result = TEMP_FAILURE_RETRY(munmap(mapped_file_, file_size_)); + if (result == -1) { + PLOG(WARNING) << "Failed unmap file '" << GetPath() << "' of size " + << file_size_; + return false; + } else { + mapped_file_ = NULL; + file_size_ = -1; + return true; + } +} + +int64_t MappedFile::Read(char* buf, int64_t byte_count, int64_t offset) const { + if (IsMapped()) { + if (offset < 0) { + errno = EINVAL; + return -errno; + } + int64_t read_size = std::max(0LL, std::min(byte_count, file_size_ - offset)); + if (read_size > 0) { + memcpy(buf, data() + offset, read_size); + } + return read_size; + } else { + return FdFile::Read(buf, byte_count, offset); + } +} + +int MappedFile::SetLength(int64_t new_length) { + CHECK(!IsMapped()); + return FdFile::SetLength(new_length); +} + +int64_t MappedFile::GetLength() const { + if (IsMapped()) { + return file_size_; + } else { + return FdFile::GetLength(); + } +} + +int MappedFile::Flush() { + int rc = IsMapped() ? TEMP_FAILURE_RETRY(msync(mapped_file_, file_size_, 0)) : FdFile::Flush(); + return rc == -1 ? -errno : 0; +} + +int64_t MappedFile::Write(const char* buf, int64_t byte_count, int64_t offset) { + if (IsMapped()) { + CHECK_EQ(kMapReadWrite, map_mode_); + if (offset < 0) { + errno = EINVAL; + return -errno; + } + int64_t write_size = std::max(0LL, std::min(byte_count, file_size_ - offset)); + if (write_size > 0) { + memcpy(data() + offset, buf, write_size); + } + return write_size; + } else { + return FdFile::Write(buf, byte_count, offset); + } +} + +int64_t MappedFile::size() const { + return GetLength(); +} + +bool MappedFile::IsMapped() const { + return mapped_file_ != NULL && mapped_file_ != MAP_FAILED; +} + +char* MappedFile::data() const { + CHECK(IsMapped()); + return static_cast<char*>(mapped_file_); +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/mapped_file.h b/runtime/base/unix_file/mapped_file.h new file mode 100644 index 0000000..28cc551 --- /dev/null +++ b/runtime/base/unix_file/mapped_file.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 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_RUNTIME_BASE_UNIX_FILE_MAPPED_FILE_H_ +#define ART_RUNTIME_BASE_UNIX_FILE_MAPPED_FILE_H_ + +#include <fcntl.h> +#include <string> +#include "base/unix_file/fd_file.h" + +namespace unix_file { + +// Random access file which handles an mmap(2), munmap(2) pair in C++ +// RAII style. When a file is mmapped, the random access file +// interface accesses the mmapped memory directly; otherwise, the +// standard file I/O is used. Whenever a function fails, it returns +// false and errno is set to the corresponding error code. +class MappedFile : public FdFile { + public: + // File modes used in Open(). + enum FileMode { + kReadOnlyMode = O_RDONLY | O_LARGEFILE, + kReadWriteMode = O_CREAT | O_RDWR | O_LARGEFILE, + }; + + MappedFile() : FdFile(), file_size_(-1), mapped_file_(NULL) { + } + // Creates a MappedFile using the given file descriptor. Takes ownership of + // the file descriptor. + explicit MappedFile(int fd) : FdFile(fd), file_size_(-1), mapped_file_(NULL) { + } + + // Unmaps and closes the file if needed. + virtual ~MappedFile(); + + // Maps an opened file to memory in the read-only mode. + bool MapReadOnly(); + + // Maps an opened file to memory in the read-write mode. Before the + // file is mapped, it is truncated to 'file_size' bytes. + bool MapReadWrite(int64_t file_size); + + // Unmaps a mapped file so that, e.g., SetLength() may be invoked. + bool Unmap(); + + // RandomAccessFile API. + // The functions below require that the file is open, but it doesn't + // have to be mapped. + virtual int Close(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + // SetLength() requires that the file is not mmapped. + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int Flush(); + // Write() requires that, if the file is mmapped, it is mmapped in + // the read-write mode. Writes past the end of file are discarded. + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + + // A convenience method equivalent to GetLength(). + int64_t size() const; + + // Returns true if the file has been mmapped. + bool IsMapped() const; + + // Returns a pointer to the start of the memory mapping once the + // file is successfully mapped; crashes otherwise. + char* data() const; + + private: + enum MapMode { + kMapReadOnly = 1, + kMapReadWrite = 2, + }; + + mutable int64_t file_size_; // May be updated in GetLength(). + void* mapped_file_; + MapMode map_mode_; + + DISALLOW_COPY_AND_ASSIGN(MappedFile); +}; + +} // namespace unix_file + +#endif // ART_RUNTIME_BASE_UNIX_FILE_MAPPED_FILE_H_ diff --git a/runtime/base/unix_file/mapped_file_test.cc b/runtime/base/unix_file/mapped_file_test.cc new file mode 100644 index 0000000..3dda02f --- /dev/null +++ b/runtime/base/unix_file/mapped_file_test.cc @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2008 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 "base/unix_file/mapped_file.h" +#include "base/logging.h" +#include "base/unix_file/fd_file.h" +#include "base/unix_file/random_access_file_test.h" +#include "base/unix_file/random_access_file_utils.h" +#include "base/unix_file/string_file.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class MappedFileTest : public RandomAccessFileTest { + protected: + MappedFileTest() : kContent("some content") { + } + + void SetUp() { + art::CommonTest::SetEnvironmentVariables(android_data_); + + good_path_ = GetTmpPath("some-file.txt"); + int fd = TEMP_FAILURE_RETRY(open(good_path_.c_str(), O_CREAT|O_RDWR, 0666)); + FdFile dst(fd); + + StringFile src; + src.Assign(kContent); + + ASSERT_TRUE(CopyFile(src, &dst)); + } + + virtual RandomAccessFile* MakeTestFile() { + TEMP_FAILURE_RETRY(truncate(good_path_.c_str(), 0)); + MappedFile* f = new MappedFile; + CHECK(f->Open(good_path_, MappedFile::kReadWriteMode)); + return f; + } + + const std::string kContent; + std::string good_path_; +}; + +TEST_F(MappedFileTest, OkayToNotUse) { + MappedFile file; + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); + EXPECT_FALSE(file.IsMapped()); +} + +TEST_F(MappedFileTest, OpenClose) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); + EXPECT_EQ(kContent.size(), file.size()); + EXPECT_EQ(0, file.Close()); + EXPECT_EQ(-1, file.Fd()); + EXPECT_FALSE(file.IsOpened()); +} + +TEST_F(MappedFileTest, OpenFdClose) { + FILE* f = tmpfile(); + ASSERT_TRUE(f != NULL); + MappedFile file(fileno(f)); + EXPECT_GE(file.Fd(), 0); + EXPECT_TRUE(file.IsOpened()); + EXPECT_EQ(0, file.Close()); +} + +TEST_F(MappedFileTest, CanUseAfterMapReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_FALSE(file.IsMapped()); + EXPECT_TRUE(file.MapReadOnly()); + EXPECT_TRUE(file.IsMapped()); + EXPECT_EQ(kContent.size(), file.size()); + ASSERT_TRUE(file.data()); + EXPECT_EQ(0, memcmp(kContent.c_str(), file.data(), file.size())); + EXPECT_EQ(0, file.Flush()); +} + +TEST_F(MappedFileTest, CanUseAfterMapReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + EXPECT_FALSE(file.IsMapped()); + EXPECT_TRUE(file.MapReadWrite(1)); + EXPECT_TRUE(file.IsMapped()); + EXPECT_EQ(1, file.size()); + ASSERT_TRUE(file.data()); + EXPECT_EQ(kContent[0], *file.data()); + EXPECT_EQ(0, file.Flush()); +} + +TEST_F(MappedFileTest, CanWriteNewData) { + const std::string new_path(GetTmpPath("new-file.txt")); + ASSERT_EQ(-1, unlink(new_path.c_str())); + ASSERT_EQ(ENOENT, errno); + + MappedFile file; + ASSERT_TRUE(file.Open(new_path, MappedFile::kReadWriteMode)); + EXPECT_TRUE(file.MapReadWrite(kContent.size())); + EXPECT_TRUE(file.IsMapped()); + EXPECT_EQ(kContent.size(), file.size()); + ASSERT_TRUE(file.data()); + memcpy(file.data(), kContent.c_str(), kContent.size()); + EXPECT_EQ(0, file.Close()); + EXPECT_FALSE(file.IsMapped()); + + FdFile new_file(TEMP_FAILURE_RETRY(open(new_path.c_str(), O_RDONLY))); + StringFile buffer; + ASSERT_TRUE(CopyFile(new_file, &buffer)); + EXPECT_EQ(kContent, buffer.ToStringPiece()); + EXPECT_EQ(0, unlink(new_path.c_str())); +} + +TEST_F(MappedFileTest, FileMustExist) { + const std::string bad_path(GetTmpPath("does-not-exist.txt")); + MappedFile file; + EXPECT_FALSE(file.Open(bad_path, MappedFile::kReadOnlyMode)); + EXPECT_EQ(-1, file.Fd()); +} + +TEST_F(MappedFileTest, FileMustBeWritable) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_FALSE(file.MapReadWrite(10)); +} + +TEST_F(MappedFileTest, RemappingAllowedUntilSuccess) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_FALSE(file.MapReadWrite(10)); + EXPECT_FALSE(file.MapReadWrite(10)); +} + +TEST_F(MappedFileTest, ResizeMappedFile) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(10)); + EXPECT_EQ(10, file.GetLength()); + EXPECT_TRUE(file.Unmap()); + EXPECT_TRUE(file.MapReadWrite(20)); + EXPECT_EQ(20, file.GetLength()); + EXPECT_EQ(0, file.Flush()); + EXPECT_TRUE(file.Unmap()); + EXPECT_EQ(0, file.Flush()); + EXPECT_EQ(0, file.SetLength(5)); + EXPECT_TRUE(file.MapReadOnly()); + EXPECT_EQ(5, file.GetLength()); +} + +TEST_F(MappedFileTest, ReadNotMapped) { + TestRead(); +} + +TEST_F(MappedFileTest, SetLengthNotMapped) { + TestSetLength(); +} + +TEST_F(MappedFileTest, WriteNotMapped) { + TestWrite(); +} + +TEST_F(MappedFileTest, ReadMappedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + TestReadContent(kContent, &file); +} + +TEST_F(MappedFileTest, ReadMappedReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(kContent.size())); + TestReadContent(kContent, &file); +} + +TEST_F(MappedFileTest, WriteMappedReadWrite) { + TEMP_FAILURE_RETRY(unlink(good_path_.c_str())); + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(kContent.size())); + + // Can't write to a negative offset. + EXPECT_EQ(-EINVAL, file.Write(kContent.c_str(), 0, -123)); + + // A zero-length write is a no-op. + EXPECT_EQ(0, file.Write(kContent.c_str(), 0, 0)); + // But the file size is as given when mapped. + EXPECT_EQ(kContent.size(), file.GetLength()); + + // Data written past the end are discarded. + EXPECT_EQ(kContent.size() - 1, + file.Write(kContent.c_str(), kContent.size(), 1)); + EXPECT_EQ(0, memcmp(kContent.c_str(), file.data() + 1, kContent.size() - 1)); + + // Data can be overwritten. + EXPECT_EQ(kContent.size(), file.Write(kContent.c_str(), kContent.size(), 0)); + EXPECT_EQ(0, memcmp(kContent.c_str(), file.data(), kContent.size())); +} + +#if 0 // death tests don't work on android yet + +class MappedFileDeathTest : public MappedFileTest {}; + +TEST_F(MappedFileDeathTest, MustMapBeforeUse) { + MappedFile file; + EXPECT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + EXPECT_DEATH(file.data(), "mapped_"); +} + +TEST_F(MappedFileDeathTest, RemappingNotAllowedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + EXPECT_DEATH(file.MapReadOnly(), "mapped_"); +} + +TEST_F(MappedFileDeathTest, RemappingNotAllowedReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(10)); + EXPECT_DEATH(file.MapReadWrite(10), "mapped_"); +} + +TEST_F(MappedFileDeathTest, SetLengthMappedReadWrite) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode)); + ASSERT_TRUE(file.MapReadWrite(10)); + EXPECT_EQ(10, file.GetLength()); + EXPECT_DEATH(file.SetLength(0), ".*"); +} + +TEST_F(MappedFileDeathTest, SetLengthMappedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + EXPECT_EQ(kContent.size(), file.GetLength()); + EXPECT_DEATH(file.SetLength(0), ".*"); +} + +TEST_F(MappedFileDeathTest, WriteMappedReadOnly) { + MappedFile file; + ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode)); + ASSERT_TRUE(file.MapReadOnly()); + char buf[10]; + EXPECT_DEATH(file.Write(buf, 0, 0), ".*"); +} + +#endif + +} // namespace unix_file diff --git a/runtime/base/unix_file/null_file.cc b/runtime/base/unix_file/null_file.cc new file mode 100644 index 0000000..050decb --- /dev/null +++ b/runtime/base/unix_file/null_file.cc @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009 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 "base/unix_file/null_file.h" +#include <errno.h> + +namespace unix_file { + +NullFile::NullFile() { +} + +NullFile::~NullFile() { +} + +int NullFile::Close() { + return 0; +} + +int NullFile::Flush() { + return 0; +} + +int64_t NullFile::Read(char* buf, int64_t byte_count, int64_t offset) const { + if (offset < 0) { + return -EINVAL; + } + return 0; +} + +int NullFile::SetLength(int64_t new_length) { + if (new_length < 0) { + return -EINVAL; + } + return 0; +} + +int64_t NullFile::GetLength() const { + return 0; +} + +int64_t NullFile::Write(const char* buf, int64_t byte_count, int64_t offset) { + if (offset < 0) { + return -EINVAL; + } + return byte_count; +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/null_file.h b/runtime/base/unix_file/null_file.h new file mode 100644 index 0000000..3394731 --- /dev/null +++ b/runtime/base/unix_file/null_file.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009 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_RUNTIME_BASE_UNIX_FILE_NULL_FILE_H_ +#define ART_RUNTIME_BASE_UNIX_FILE_NULL_FILE_H_ + +#include "base/unix_file/random_access_file.h" +#include "base/macros.h" + +namespace unix_file { + +// A RandomAccessFile implementation equivalent to /dev/null. Writes are +// discarded, and there's no data to be read. Callers could use FdFile in +// conjunction with /dev/null, but that's not portable and costs a file +// descriptor. NullFile is "free". +// +// Thread safe. +class NullFile : public RandomAccessFile { + public: + NullFile(); + virtual ~NullFile(); + + // RandomAccessFile API. + virtual int Close(); + virtual int Flush(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + + private: + DISALLOW_COPY_AND_ASSIGN(NullFile); +}; + +} // namespace unix_file + +#endif // ART_RUNTIME_BASE_UNIX_FILE_NULL_FILE_H_ diff --git a/runtime/base/unix_file/null_file_test.cc b/runtime/base/unix_file/null_file_test.cc new file mode 100644 index 0000000..0f20acd --- /dev/null +++ b/runtime/base/unix_file/null_file_test.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009 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 "base/unix_file/null_file.h" + +#include <errno.h> + +#include "gtest/gtest.h" + +namespace unix_file { + +class NullFileTest : public testing::Test { }; + +TEST_F(NullFileTest, Read) { + NullFile f; + char buf[256]; + // You can't read a negative number of bytes... + ASSERT_EQ(-EINVAL, f.Read(buf, 0, -1)); + // ...but everything else is fine (though you'll get no data). + ASSERT_EQ(0, f.Read(buf, 128, 0)); + ASSERT_EQ(0, f.Read(buf, 128, 128)); +} + +TEST_F(NullFileTest, SetLength) { + NullFile f; + // You can't set a negative length... + ASSERT_EQ(-EINVAL, f.SetLength(-1)); + // ...but everything else is fine. + ASSERT_EQ(0, f.SetLength(0)); + ASSERT_EQ(0, f.SetLength(128)); +} + +TEST_F(NullFileTest, GetLength) { + const std::string content("hello"); + NullFile f; + // The length is always 0. + ASSERT_EQ(0, f.GetLength()); + ASSERT_EQ(content.size(), f.Write(content.data(), content.size(), 0)); + ASSERT_EQ(0, f.GetLength()); +} + +TEST_F(NullFileTest, Write) { + const std::string content("hello"); + NullFile f; + // You can't write at a negative offset... + ASSERT_EQ(-EINVAL, f.Write(content.data(), content.size(), -128)); + // But you can write anywhere else... + ASSERT_EQ(content.size(), f.Write(content.data(), content.size(), 0)); + ASSERT_EQ(content.size(), f.Write(content.data(), content.size(), 128)); + // ...though the file will remain empty. + ASSERT_EQ(0, f.GetLength()); +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/random_access_file.h b/runtime/base/unix_file/random_access_file.h new file mode 100644 index 0000000..31a6dbe --- /dev/null +++ b/runtime/base/unix_file/random_access_file.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 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_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_H_ +#define ART_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_H_ + +#include <stdint.h> + +namespace unix_file { + +// A file interface supporting random-access reading and writing of content, +// along with the ability to set the length of a file (smaller or greater than +// its current extent). +// +// This interface does not support a stream position (i.e. every read or write +// must specify an offset). This interface does not imply any buffering policy. +// +// All operations return >= 0 on success or -errno on failure. +// +// Implementations never return EINTR; callers are spared the need to manually +// retry interrupted operations. +// +// Any concurrent access to files should be externally synchronized. +class RandomAccessFile { + public: + virtual ~RandomAccessFile() { } + + virtual int Close() = 0; + + // Reads 'byte_count' bytes into 'buf' starting at offset 'offset' in the + // file. Returns the number of bytes actually read. + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const = 0; + + // Sets the length of the file to 'new_length'. If this is smaller than the + // file's current extent, data is discarded. If this is greater than the + // file's current extent, it is as if a write of the relevant number of zero + // bytes occurred. Returns 0 on success. + virtual int SetLength(int64_t new_length) = 0; + + // Returns the current size of this file. + virtual int64_t GetLength() const = 0; + + // Writes 'byte_count' bytes from 'buf' starting at offset 'offset' in the + // file. Zero-byte writes are acceptable, and writes past the end are as if + // a write of the relevant number of zero bytes also occurred. Returns the + // number of bytes actually written. + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset) = 0; + + // Flushes file data. + virtual int Flush() = 0; +}; + +} // namespace unix_file + +#endif // ART_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_H_ diff --git a/runtime/base/unix_file/random_access_file_test.h b/runtime/base/unix_file/random_access_file_test.h new file mode 100644 index 0000000..9d8550d --- /dev/null +++ b/runtime/base/unix_file/random_access_file_test.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2009 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_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_TEST_H_ +#define ART_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_TEST_H_ + +#include <errno.h> + +#include <string> + +#include "common_test.h" +#include "gtest/gtest.h" +#include "UniquePtr.h" + +namespace unix_file { + +class RandomAccessFileTest : public testing::Test { + protected: + virtual ~RandomAccessFileTest() { + } + + // Override this to return an instance of the subclass under test that's + // backed by a temporary file. + virtual RandomAccessFile* MakeTestFile() = 0; + + virtual void SetUp() { + art::CommonTest::SetEnvironmentVariables(android_data_); + } + + std::string GetTmpPath(const std::string& name) { + std::string path; + path = android_data_; + path += "/"; + path += name; + return path; + } + + // TODO(enh): ReadString (and WriteString) might be generally useful. + static bool ReadString(RandomAccessFile* f, std::string* s) { + s->clear(); + char buf[256]; + int64_t n = 0; + int64_t offset = 0; + while ((n = f->Read(buf, sizeof(buf), offset)) > 0) { + s->append(buf, n); + offset += n; + } + return n != -1; + } + + void TestRead() { + char buf[256]; + UniquePtr<RandomAccessFile> file(MakeTestFile()); + + // Reading from the start of an empty file gets you zero bytes, however many + // you ask for. + ASSERT_EQ(0, file->Read(buf, 0, 0)); + ASSERT_EQ(0, file->Read(buf, 123, 0)); + + const std::string content("hello"); + ASSERT_EQ(content.size(), file->Write(content.data(), content.size(), 0)); + + TestReadContent(content, file.get()); + } + + void TestReadContent(const std::string& content, RandomAccessFile* file) { + const int buf_size = content.size() + 10; + UniquePtr<char> buf(new char[buf_size]); + // Can't read from a negative offset. + ASSERT_EQ(-EINVAL, file->Read(buf.get(), 0, -123)); + + // Reading too much gets us just what's in the file. + ASSERT_EQ(content.size(), file->Read(buf.get(), buf_size, 0)); + ASSERT_EQ(std::string(buf.get(), content.size()), content); + + // We only get as much as we ask for. + const size_t short_request = 2; + ASSERT_LT(short_request, content.size()); + ASSERT_EQ(short_request, file->Read(buf.get(), short_request, 0)); + ASSERT_EQ(std::string(buf.get(), short_request), + content.substr(0, short_request)); + + // We don't have to start at the beginning. + const int non_zero_offset = 2; + ASSERT_GT(non_zero_offset, 0); + ASSERT_EQ(short_request, + file->Read(buf.get(), short_request, non_zero_offset)); + ASSERT_EQ(std::string(buf.get(), short_request), + content.substr(non_zero_offset, short_request)); + + // Reading past the end gets us nothing. + ASSERT_EQ(0, file->Read(buf.get(), buf_size, file->GetLength())); + ASSERT_EQ(0, file->Read(buf.get(), buf_size, file->GetLength() + 1)); + } + + void TestSetLength() { + const std::string content("hello"); + UniquePtr<RandomAccessFile> file(MakeTestFile()); + ASSERT_EQ(content.size(), file->Write(content.data(), content.size(), 0)); + ASSERT_EQ(content.size(), file->GetLength()); + + // Can't give a file a negative length. + ASSERT_EQ(-EINVAL, file->SetLength(-123)); + + // Can truncate the file. + int64_t new_length = 2; + ASSERT_EQ(0, file->SetLength(new_length)); + ASSERT_EQ(new_length, file->GetLength()); + std::string new_content; + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ(content.substr(0, 2), new_content); + + // Expanding the file appends zero bytes. + new_length = file->GetLength() + 1; + ASSERT_EQ(0, file->SetLength(new_length)); + ASSERT_EQ(new_length, file->GetLength()); + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ('\0', new_content[new_length - 1]); + } + + void TestWrite() { + const std::string content("hello"); + UniquePtr<RandomAccessFile> file(MakeTestFile()); + + // Can't write to a negative offset. + ASSERT_EQ(-EINVAL, file->Write(content.data(), 0, -123)); + + // Writing zero bytes of data is a no-op. + ASSERT_EQ(0, file->Write(content.data(), 0, 0)); + ASSERT_EQ(0, file->GetLength()); + + // We can write data. + ASSERT_EQ(content.size(), file->Write(content.data(), content.size(), 0)); + ASSERT_EQ(content.size(), file->GetLength()); + std::string new_content; + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ(new_content, content); + + // We can read it back. + char buf[256]; + ASSERT_EQ(content.size(), file->Read(buf, sizeof(buf), 0)); + ASSERT_EQ(std::string(buf, content.size()), content); + + // We can append data past the end. + ASSERT_EQ(content.size(), + file->Write(content.data(), content.size(), file->GetLength() + 1)); + int64_t new_length = 2*content.size() + 1; + ASSERT_EQ(file->GetLength(), new_length); + ASSERT_TRUE(ReadString(file.get(), &new_content)); + ASSERT_EQ(std::string("hello\0hello", new_length), new_content); + } + + protected: + std::string android_data_; +}; + +} // namespace unix_file + +#endif // ART_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_TEST_H_ diff --git a/runtime/base/unix_file/random_access_file_utils.cc b/runtime/base/unix_file/random_access_file_utils.cc new file mode 100644 index 0000000..df3b308 --- /dev/null +++ b/runtime/base/unix_file/random_access_file_utils.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009 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 <vector> +#include "base/unix_file/random_access_file_utils.h" +#include "base/unix_file/random_access_file.h" + +namespace unix_file { + +bool CopyFile(const RandomAccessFile& src, RandomAccessFile* dst) { + // We don't call src->GetLength because some files (those in /proc, say) + // don't know how long they are. We just read until there's nothing left. + std::vector<char> buf(4096); + int64_t offset = 0; + int64_t n; + while ((n = src.Read(&buf[0], buf.size(), offset)) > 0) { + if (dst->Write(&buf[0], n, offset) != n) { + return false; + } + offset += n; + } + return n >= 0; +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/random_access_file_utils.h b/runtime/base/unix_file/random_access_file_utils.h new file mode 100644 index 0000000..30c81c0 --- /dev/null +++ b/runtime/base/unix_file/random_access_file_utils.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 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_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_UTILS_H_ +#define ART_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_UTILS_H_ + +namespace unix_file { + +class RandomAccessFile; + +// Copies from 'src' to 'dst'. Reads all the data from 'src', and writes it +// to 'dst'. Not thread-safe. Neither file will be closed. +bool CopyFile(const RandomAccessFile& src, RandomAccessFile* dst); + +} // namespace unix_file + +#endif // ART_RUNTIME_BASE_UNIX_FILE_RANDOM_ACCESS_FILE_UTILS_H_ diff --git a/runtime/base/unix_file/random_access_file_utils_test.cc b/runtime/base/unix_file/random_access_file_utils_test.cc new file mode 100644 index 0000000..6317922 --- /dev/null +++ b/runtime/base/unix_file/random_access_file_utils_test.cc @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 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 "base/unix_file/random_access_file_utils.h" +#include "base/unix_file/fd_file.h" +#include "base/unix_file/string_file.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class RandomAccessFileUtilsTest : public testing::Test { }; + +TEST_F(RandomAccessFileUtilsTest, CopyFile) { + StringFile src; + StringFile dst; + + const std::string content("hello"); + src.Assign(content); + ASSERT_EQ(src.ToStringPiece(), content); + ASSERT_EQ(dst.ToStringPiece(), ""); + + ASSERT_TRUE(CopyFile(src, &dst)); + ASSERT_EQ(src.ToStringPiece(), dst.ToStringPiece()); +} + +TEST_F(RandomAccessFileUtilsTest, BadSrc) { + FdFile src(-1); + StringFile dst; + ASSERT_FALSE(CopyFile(src, &dst)); +} + +TEST_F(RandomAccessFileUtilsTest, BadDst) { + StringFile src; + FdFile dst(-1); + + // We need some source content to trigger a write. + // Copying an empty file is a no-op. + src.Assign("hello"); + + ASSERT_FALSE(CopyFile(src, &dst)); +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/string_file.cc b/runtime/base/unix_file/string_file.cc new file mode 100644 index 0000000..ff0d0fa --- /dev/null +++ b/runtime/base/unix_file/string_file.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2009 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 "base/unix_file/string_file.h" +#include <errno.h> +#include <algorithm> +#include "base/logging.h" + +namespace unix_file { + +StringFile::StringFile() { +} + +StringFile::~StringFile() { +} + +int StringFile::Close() { + return 0; +} + +int StringFile::Flush() { + return 0; +} + +int64_t StringFile::Read(char *buf, int64_t byte_count, int64_t offset) const { + CHECK(buf); + CHECK_GE(byte_count, 0); + + if (offset < 0) { + return -EINVAL; + } + + const int64_t available_bytes = std::min(byte_count, GetLength() - offset); + if (available_bytes < 0) { + return 0; // Not an error, but nothing for us to do, either. + } + memcpy(buf, data_.data() + offset, available_bytes); + return available_bytes; +} + +int StringFile::SetLength(int64_t new_length) { + if (new_length < 0) { + return -EINVAL; + } + data_.resize(new_length); + return 0; +} + +int64_t StringFile::GetLength() const { + return data_.size(); +} + +int64_t StringFile::Write(const char *buf, int64_t byte_count, int64_t offset) { + CHECK(buf); + CHECK_GE(byte_count, 0); + + if (offset < 0) { + return -EINVAL; + } + + if (byte_count == 0) { + return 0; + } + + // FUSE seems happy to allow writes past the end. (I'd guess it doesn't + // synthesize a write of zero bytes so that we're free to implement sparse + // files.) GNU as(1) seems to require such writes. Those files are small. + const int64_t bytes_past_end = offset - GetLength(); + if (bytes_past_end > 0) { + data_.append(bytes_past_end, '\0'); + } + + data_.replace(offset, byte_count, buf, byte_count); + return byte_count; +} + +void StringFile::Assign(const art::StringPiece &new_data) { + data_.assign(new_data.data(), new_data.size()); +} + +const art::StringPiece StringFile::ToStringPiece() const { + return data_; +} + +} // namespace unix_file diff --git a/runtime/base/unix_file/string_file.h b/runtime/base/unix_file/string_file.h new file mode 100644 index 0000000..26904f8 --- /dev/null +++ b/runtime/base/unix_file/string_file.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 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_RUNTIME_BASE_UNIX_FILE_STRING_FILE_H_ +#define ART_RUNTIME_BASE_UNIX_FILE_STRING_FILE_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "base/stringpiece.h" +#include "base/unix_file/random_access_file.h" + +namespace unix_file { + +// A RandomAccessFile implementation backed by a std::string. (That is, all data is +// kept in memory.) +// +// Not thread safe. +class StringFile : public RandomAccessFile { + public: + StringFile(); + virtual ~StringFile(); + + // RandomAccessFile API. + virtual int Close(); + virtual int Flush(); + virtual int64_t Read(char* buf, int64_t byte_count, int64_t offset) const; + virtual int SetLength(int64_t new_length); + virtual int64_t GetLength() const; + virtual int64_t Write(const char* buf, int64_t byte_count, int64_t offset); + + // Bonus API. + void Assign(const art::StringPiece& new_data); + const art::StringPiece ToStringPiece() const; + + private: + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(StringFile); +}; + +} // namespace unix_file + +#endif // ART_RUNTIME_BASE_UNIX_FILE_STRING_FILE_H_ diff --git a/runtime/base/unix_file/string_file_test.cc b/runtime/base/unix_file/string_file_test.cc new file mode 100644 index 0000000..8821461 --- /dev/null +++ b/runtime/base/unix_file/string_file_test.cc @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009 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 "base/unix_file/string_file.h" +#include "base/unix_file/random_access_file_test.h" +#include "gtest/gtest.h" + +namespace unix_file { + +class StringFileTest : public RandomAccessFileTest { + protected: + virtual RandomAccessFile* MakeTestFile() { + return new StringFile; + } +}; + +TEST_F(StringFileTest, Read) { + TestRead(); +} + +TEST_F(StringFileTest, SetLength) { + TestSetLength(); +} + +TEST_F(StringFileTest, Write) { + TestWrite(); +} + +} // namespace unix_file |