http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/d6abb29d/be/src/kudu/util/metrics-test.cc ---------------------------------------------------------------------- diff --git a/be/src/kudu/util/metrics-test.cc b/be/src/kudu/util/metrics-test.cc new file mode 100644 index 0000000..7493056 --- /dev/null +++ b/be/src/kudu/util/metrics-test.cc @@ -0,0 +1,309 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 <rapidjson/document.h> +#include <string> +#include <unordered_set> +#include <vector> + +#include "kudu/gutil/bind.h" +#include "kudu/gutil/map-util.h" +#include "kudu/util/hdr_histogram.h" +#include "kudu/util/jsonreader.h" +#include "kudu/util/jsonwriter.h" +#include "kudu/util/metrics.h" +#include "kudu/util/test_util.h" + +using std::string; +using std::unordered_set; +using std::vector; + +DECLARE_int32(metrics_retirement_age_ms); + +namespace kudu { + +METRIC_DEFINE_entity(test_entity); + +class MetricsTest : public KuduTest { + public: + void SetUp() override { + KuduTest::SetUp(); + + entity_ = METRIC_ENTITY_test_entity.Instantiate(®istry_, "my-test"); + } + + protected: + MetricRegistry registry_; + scoped_refptr<MetricEntity> entity_; +}; + +METRIC_DEFINE_counter(test_entity, reqs_pending, "Requests Pending", MetricUnit::kRequests, + "Number of requests pending"); + +TEST_F(MetricsTest, SimpleCounterTest) { + scoped_refptr<Counter> requests = + new Counter(&METRIC_reqs_pending); + ASSERT_EQ("Number of requests pending", requests->prototype()->description()); + ASSERT_EQ(0, requests->value()); + requests->Increment(); + ASSERT_EQ(1, requests->value()); + requests->IncrementBy(2); + ASSERT_EQ(3, requests->value()); +} + +METRIC_DEFINE_gauge_uint64(test_entity, fake_memory_usage, "Memory Usage", + MetricUnit::kBytes, "Test Gauge 1"); + +TEST_F(MetricsTest, SimpleAtomicGaugeTest) { + scoped_refptr<AtomicGauge<uint64_t> > mem_usage = + METRIC_fake_memory_usage.Instantiate(entity_, 0); + ASSERT_EQ(METRIC_fake_memory_usage.description(), mem_usage->prototype()->description()); + ASSERT_EQ(0, mem_usage->value()); + mem_usage->IncrementBy(7); + ASSERT_EQ(7, mem_usage->value()); + mem_usage->set_value(5); + ASSERT_EQ(5, mem_usage->value()); +} + +METRIC_DEFINE_gauge_int64(test_entity, test_func_gauge, "Test Gauge", MetricUnit::kBytes, + "Test Gauge 2"); + +static int64_t MyFunction(int* metric_val) { + return (*metric_val)++; +} + +TEST_F(MetricsTest, SimpleFunctionGaugeTest) { + int metric_val = 1000; + scoped_refptr<FunctionGauge<int64_t> > gauge = + METRIC_test_func_gauge.InstantiateFunctionGauge( + entity_, Bind(&MyFunction, Unretained(&metric_val))); + + ASSERT_EQ(1000, gauge->value()); + ASSERT_EQ(1001, gauge->value()); + + gauge->DetachToCurrentValue(); + // After detaching, it should continue to return the same constant value. + ASSERT_EQ(1002, gauge->value()); + ASSERT_EQ(1002, gauge->value()); + + // Test resetting to a constant. + gauge->DetachToConstant(2); + ASSERT_EQ(2, gauge->value()); +} + +TEST_F(MetricsTest, AutoDetachToLastValue) { + int metric_val = 1000; + scoped_refptr<FunctionGauge<int64_t> > gauge = + METRIC_test_func_gauge.InstantiateFunctionGauge( + entity_, Bind(&MyFunction, Unretained(&metric_val))); + + ASSERT_EQ(1000, gauge->value()); + ASSERT_EQ(1001, gauge->value()); + { + FunctionGaugeDetacher detacher; + gauge->AutoDetachToLastValue(&detacher); + ASSERT_EQ(1002, gauge->value()); + ASSERT_EQ(1003, gauge->value()); + } + + ASSERT_EQ(1004, gauge->value()); + ASSERT_EQ(1004, gauge->value()); +} + +TEST_F(MetricsTest, AutoDetachToConstant) { + int metric_val = 1000; + scoped_refptr<FunctionGauge<int64_t> > gauge = + METRIC_test_func_gauge.InstantiateFunctionGauge( + entity_, Bind(&MyFunction, Unretained(&metric_val))); + + ASSERT_EQ(1000, gauge->value()); + ASSERT_EQ(1001, gauge->value()); + { + FunctionGaugeDetacher detacher; + gauge->AutoDetach(&detacher, 12345); + ASSERT_EQ(1002, gauge->value()); + ASSERT_EQ(1003, gauge->value()); + } + + ASSERT_EQ(12345, gauge->value()); +} + +METRIC_DEFINE_gauge_uint64(test_entity, counter_as_gauge, "Gauge exposed as Counter", + MetricUnit::kBytes, "Gauge exposed as Counter", + EXPOSE_AS_COUNTER); +TEST_F(MetricsTest, TEstExposeGaugeAsCounter) { + ASSERT_EQ(MetricType::kCounter, METRIC_counter_as_gauge.type()); +} + +METRIC_DEFINE_histogram(test_entity, test_hist, "Test Histogram", + MetricUnit::kMilliseconds, "foo", 1000000, 3); + +TEST_F(MetricsTest, SimpleHistogramTest) { + scoped_refptr<Histogram> hist = METRIC_test_hist.Instantiate(entity_); + hist->Increment(2); + hist->IncrementBy(4, 1); + ASSERT_EQ(2, hist->histogram_->MinValue()); + ASSERT_EQ(3, hist->histogram_->MeanValue()); + ASSERT_EQ(4, hist->histogram_->MaxValue()); + ASSERT_EQ(2, hist->histogram_->TotalCount()); + ASSERT_EQ(6, hist->histogram_->TotalSum()); + // TODO: Test coverage needs to be improved a lot. +} + +TEST_F(MetricsTest, JsonPrintTest) { + scoped_refptr<Counter> bytes_seen = METRIC_reqs_pending.Instantiate(entity_); + bytes_seen->Increment(); + entity_->SetAttribute("test_attr", "attr_val"); + + // Generate the JSON. + std::ostringstream out; + JsonWriter writer(&out, JsonWriter::PRETTY); + ASSERT_OK(entity_->WriteAsJson(&writer, { "*" }, MetricJsonOptions())); + + // Now parse it back out. + JsonReader reader(out.str()); + ASSERT_OK(reader.Init()); + + vector<const rapidjson::Value*> metrics; + ASSERT_OK(reader.ExtractObjectArray(reader.root(), "metrics", &metrics)); + ASSERT_EQ(1, metrics.size()); + string metric_name; + ASSERT_OK(reader.ExtractString(metrics[0], "name", &metric_name)); + ASSERT_EQ("reqs_pending", metric_name); + int64_t metric_value; + ASSERT_OK(reader.ExtractInt64(metrics[0], "value", &metric_value)); + ASSERT_EQ(1L, metric_value); + + const rapidjson::Value* attributes; + ASSERT_OK(reader.ExtractObject(reader.root(), "attributes", &attributes)); + string attr_value; + ASSERT_OK(reader.ExtractString(attributes, "test_attr", &attr_value)); + ASSERT_EQ("attr_val", attr_value); + + // Verify that, if we filter for a metric that isn't in this entity, we get no result. + out.str(""); + ASSERT_OK(entity_->WriteAsJson(&writer, { "not_a_matching_metric" }, MetricJsonOptions())); + ASSERT_EQ("", out.str()); +} + +// Test that metrics are retired when they are no longer referenced. +TEST_F(MetricsTest, RetirementTest) { + FLAGS_metrics_retirement_age_ms = 100; + + const string kMetricName = "foo"; + scoped_refptr<Counter> counter = METRIC_reqs_pending.Instantiate(entity_); + ASSERT_EQ(1, entity_->UnsafeMetricsMapForTests().size()); + + // Since we hold a reference to the counter, it should not get retired. + entity_->RetireOldMetrics(); + ASSERT_EQ(1, entity_->UnsafeMetricsMapForTests().size()); + + // When we de-ref it, it should not get immediately retired, either, because + // we keep retirable metrics around for some amount of time. We try retiring + // a number of times to hit all the cases. + counter = nullptr; + for (int i = 0; i < 3; i++) { + entity_->RetireOldMetrics(); + ASSERT_EQ(1, entity_->UnsafeMetricsMapForTests().size()); + } + + // If we wait for longer than the retirement time, and call retire again, we'll + // actually retire it. + SleepFor(MonoDelta::FromMilliseconds(FLAGS_metrics_retirement_age_ms * 1.5)); + entity_->RetireOldMetrics(); + ASSERT_EQ(0, entity_->UnsafeMetricsMapForTests().size()); +} + +TEST_F(MetricsTest, TestRetiringEntities) { + ASSERT_EQ(1, registry_.num_entities()); + + // Drop the reference to our entity. + entity_.reset(); + + // Retire metrics. Since there is nothing inside our entity, it should + // retire immediately (no need to loop). + registry_.RetireOldMetrics(); + + ASSERT_EQ(0, registry_.num_entities()); +} + +// Test that we can mark a metric to never be retired. +TEST_F(MetricsTest, NeverRetireTest) { + entity_->NeverRetire(METRIC_test_hist.Instantiate(entity_)); + FLAGS_metrics_retirement_age_ms = 0; + + for (int i = 0; i < 3; i++) { + entity_->RetireOldMetrics(); + ASSERT_EQ(1, entity_->UnsafeMetricsMapForTests().size()); + } +} + +TEST_F(MetricsTest, TestInstantiatingTwice) { + // Test that re-instantiating the same entity ID returns the same object. + scoped_refptr<MetricEntity> new_entity = METRIC_ENTITY_test_entity.Instantiate( + ®istry_, entity_->id()); + ASSERT_EQ(new_entity.get(), entity_.get()); +} + +TEST_F(MetricsTest, TestInstantiatingDifferentEntities) { + scoped_refptr<MetricEntity> new_entity = METRIC_ENTITY_test_entity.Instantiate( + ®istry_, "some other ID"); + ASSERT_NE(new_entity.get(), entity_.get()); +} + +TEST_F(MetricsTest, TestDumpJsonPrototypes) { + // Dump the prototype info. + std::ostringstream out; + JsonWriter w(&out, JsonWriter::PRETTY); + MetricPrototypeRegistry::get()->WriteAsJson(&w); + string json = out.str(); + + // Quick sanity check for one of our metrics defined in this file. + const char* expected = + " {\n" + " \"name\": \"test_func_gauge\",\n" + " \"label\": \"Test Gauge\",\n" + " \"type\": \"gauge\",\n" + " \"unit\": \"bytes\",\n" + " \"description\": \"Test Gauge 2\",\n" + " \"entity_type\": \"test_entity\"\n" + " }"; + ASSERT_STR_CONTAINS(json, expected); + + // Parse it. + rapidjson::Document d; + d.Parse<0>(json.c_str()); + + // Ensure that we got a reasonable number of metrics. + int num_metrics = d["metrics"].Size(); + int num_entities = d["entities"].Size(); + LOG(INFO) << "Parsed " << num_metrics << " metrics and " << num_entities << " entities"; + ASSERT_GT(num_metrics, 5); + ASSERT_EQ(num_entities, 2); + + // Spot-check that some metrics were properly registered and that the JSON was properly + // formed. + unordered_set<string> seen_metrics; + for (int i = 0; i < d["metrics"].Size(); i++) { + InsertOrDie(&seen_metrics, d["metrics"][i]["name"].GetString()); + } + ASSERT_TRUE(ContainsKey(seen_metrics, "threads_started")); + ASSERT_TRUE(ContainsKey(seen_metrics, "test_hist")); +} + +} // namespace kudu
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/d6abb29d/be/src/kudu/util/metrics.cc ---------------------------------------------------------------------- diff --git a/be/src/kudu/util/metrics.cc b/be/src/kudu/util/metrics.cc new file mode 100644 index 0000000..079bf89 --- /dev/null +++ b/be/src/kudu/util/metrics.cc @@ -0,0 +1,686 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 "kudu/util/metrics.h" + +#include <iostream> +#include <sstream> +#include <map> +#include <set> + +#include <gflags/gflags.h> + +#include "kudu/gutil/atomicops.h" +#include "kudu/gutil/casts.h" +#include "kudu/gutil/map-util.h" +#include "kudu/gutil/singleton.h" +#include "kudu/gutil/stl_util.h" +#include "kudu/gutil/strings/substitute.h" +#include "kudu/util/flag_tags.h" +#include "kudu/util/hdr_histogram.h" +#include "kudu/util/histogram.pb.h" +#include "kudu/util/jsonwriter.h" +#include "kudu/util/locks.h" +#include "kudu/util/status.h" + +DEFINE_int32(metrics_retirement_age_ms, 120 * 1000, + "The minimum number of milliseconds a metric will be kept for after it is " + "no longer active. (Advanced option)"); +TAG_FLAG(metrics_retirement_age_ms, runtime); +TAG_FLAG(metrics_retirement_age_ms, advanced); + +// Process/server-wide metrics should go into the 'server' entity. +// More complex applications will define other entities. +METRIC_DEFINE_entity(server); + +namespace kudu { + +using std::string; +using std::vector; +using strings::Substitute; + +// +// MetricUnit +// + +const char* MetricUnit::Name(Type unit) { + switch (unit) { + case kCacheHits: + return "hits"; + case kCacheQueries: + return "queries"; + case kBytes: + return "bytes"; + case kRequests: + return "requests"; + case kEntries: + return "entries"; + case kRows: + return "rows"; + case kCells: + return "cells"; + case kConnections: + return "connections"; + case kOperations: + return "operations"; + case kProbes: + return "probes"; + case kNanoseconds: + return "nanoseconds"; + case kMicroseconds: + return "microseconds"; + case kMilliseconds: + return "milliseconds"; + case kSeconds: + return "seconds"; + case kThreads: + return "threads"; + case kTransactions: + return "transactions"; + case kUnits: + return "units"; + case kScanners: + return "scanners"; + case kMaintenanceOperations: + return "operations"; + case kBlocks: + return "blocks"; + case kLogBlockContainers: + return "log block containers"; + case kTasks: + return "tasks"; + case kMessages: + return "messages"; + case kContextSwitches: + return "context switches"; + case kDataDirectories: + return "data directories"; + default: + DCHECK(false) << "Unknown unit with type = " << unit; + return "UNKNOWN UNIT"; + } +} + +// +// MetricType +// + +const char* const MetricType::kGaugeType = "gauge"; +const char* const MetricType::kCounterType = "counter"; +const char* const MetricType::kHistogramType = "histogram"; +const char* MetricType::Name(MetricType::Type type) { + switch (type) { + case kGauge: + return kGaugeType; + case kCounter: + return kCounterType; + case kHistogram: + return kHistogramType; + default: + return "UNKNOWN TYPE"; + } +} + +// +// MetricEntityPrototype +// + +MetricEntityPrototype::MetricEntityPrototype(const char* name) + : name_(name) { + MetricPrototypeRegistry::get()->AddEntity(this); +} + +MetricEntityPrototype::~MetricEntityPrototype() { +} + +scoped_refptr<MetricEntity> MetricEntityPrototype::Instantiate( + MetricRegistry* registry, + const std::string& id, + const MetricEntity::AttributeMap& initial_attrs) const { + return registry->FindOrCreateEntity(this, id, initial_attrs); +} + + +// +// MetricEntity +// + +MetricEntity::MetricEntity(const MetricEntityPrototype* prototype, + std::string id, AttributeMap attributes) + : prototype_(prototype), + id_(std::move(id)), + attributes_(std::move(attributes)) {} + +MetricEntity::~MetricEntity() { +} + +void MetricEntity::CheckInstantiation(const MetricPrototype* proto) const { + CHECK_STREQ(prototype_->name(), proto->entity_type()) + << "Metric " << proto->name() << " may not be instantiated entity of type " + << prototype_->name() << " (expected: " << proto->entity_type() << ")"; +} + +scoped_refptr<Metric> MetricEntity::FindOrNull(const MetricPrototype& prototype) const { + std::lock_guard<simple_spinlock> l(lock_); + return FindPtrOrNull(metric_map_, &prototype); +} + +namespace { + +bool MatchMetricInList(const string& metric_name, + const vector<string>& match_params) { + for (const string& param : match_params) { + // Handle wildcard. + if (param == "*") return true; + // The parameter is a substring match of the metric name. + if (metric_name.find(param) != std::string::npos) { + return true; + } + } + return false; +} + +} // anonymous namespace + + +Status MetricEntity::WriteAsJson(JsonWriter* writer, + const vector<string>& requested_metrics, + const MetricJsonOptions& opts) const { + bool select_all = MatchMetricInList(id(), requested_metrics); + + // We want the keys to be in alphabetical order when printing, so we use an ordered map here. + typedef std::map<const char*, scoped_refptr<Metric> > OrderedMetricMap; + OrderedMetricMap metrics; + AttributeMap attrs; + { + // Snapshot the metrics in this registry (not guaranteed to be a consistent snapshot) + std::lock_guard<simple_spinlock> l(lock_); + attrs = attributes_; + for (const MetricMap::value_type& val : metric_map_) { + const MetricPrototype* prototype = val.first; + const scoped_refptr<Metric>& metric = val.second; + + if (select_all || MatchMetricInList(prototype->name(), requested_metrics)) { + InsertOrDie(&metrics, prototype->name(), metric); + } + } + } + + // If we had a filter, and we didn't either match this entity or any metrics inside + // it, don't print the entity at all. + if (!requested_metrics.empty() && !select_all && metrics.empty()) { + return Status::OK(); + } + + writer->StartObject(); + + writer->String("type"); + writer->String(prototype_->name()); + + writer->String("id"); + writer->String(id_); + + writer->String("attributes"); + writer->StartObject(); + for (const AttributeMap::value_type& val : attrs) { + writer->String(val.first); + writer->String(val.second); + } + writer->EndObject(); + + writer->String("metrics"); + writer->StartArray(); + for (OrderedMetricMap::value_type& val : metrics) { + WARN_NOT_OK(val.second->WriteAsJson(writer, opts), + strings::Substitute("Failed to write $0 as JSON", val.first)); + + } + writer->EndArray(); + + writer->EndObject(); + + return Status::OK(); +} + +void MetricEntity::RetireOldMetrics() { + MonoTime now(MonoTime::Now()); + + std::lock_guard<simple_spinlock> l(lock_); + for (auto it = metric_map_.begin(); it != metric_map_.end();) { + const scoped_refptr<Metric>& metric = it->second; + + if (PREDICT_TRUE(!metric->HasOneRef())) { + // The metric is still in use. Note that, in the case of "NeverRetire()", the metric + // will have a ref-count of 2 because it is reffed by the 'never_retire_metrics_' + // collection. + + // Ensure that it is not marked for later retirement (this could happen in the case + // that a metric is un-reffed and then re-reffed later by looking it up from the + // registry). + metric->retire_time_ = MonoTime(); + ++it; + continue; + } + + if (!metric->retire_time_.Initialized()) { + VLOG(3) << "Metric " << it->first << " has become un-referenced. Will retire after " + << "the retention interval"; + // This is the first time we've seen this metric as retirable. + metric->retire_time_ = + now + MonoDelta::FromMilliseconds(FLAGS_metrics_retirement_age_ms); + ++it; + continue; + } + + // If we've already seen this metric in a previous scan, check if it's + // time to retire it yet. + if (now < metric->retire_time_) { + VLOG(3) << "Metric " << it->first << " is un-referenced, but still within " + << "the retention interval"; + ++it; + continue; + } + + + VLOG(2) << "Retiring metric " << it->first; + metric_map_.erase(it++); + } +} + +void MetricEntity::NeverRetire(const scoped_refptr<Metric>& metric) { + std::lock_guard<simple_spinlock> l(lock_); + never_retire_metrics_.push_back(metric); +} + +void MetricEntity::SetAttributes(const AttributeMap& attrs) { + std::lock_guard<simple_spinlock> l(lock_); + attributes_ = attrs; +} + +void MetricEntity::SetAttribute(const string& key, const string& val) { + std::lock_guard<simple_spinlock> l(lock_); + attributes_[key] = val; +} + +// +// MetricRegistry +// + +MetricRegistry::MetricRegistry() { +} + +MetricRegistry::~MetricRegistry() { +} + +Status MetricRegistry::WriteAsJson(JsonWriter* writer, + const vector<string>& requested_metrics, + const MetricJsonOptions& opts) const { + EntityMap entities; + { + std::lock_guard<simple_spinlock> l(lock_); + entities = entities_; + } + + writer->StartArray(); + for (const EntityMap::value_type e : entities) { + WARN_NOT_OK(e.second->WriteAsJson(writer, requested_metrics, opts), + Substitute("Failed to write entity $0 as JSON", e.second->id())); + } + writer->EndArray(); + + // Rather than having a thread poll metrics periodically to retire old ones, + // we'll just retire them here. The only downside is that, if no one is polling + // metrics, we may end up leaving them around indefinitely; however, metrics are + // small, and one might consider it a feature: if monitoring stops polling for + // metrics, we should keep them around until the next poll. + entities.clear(); // necessary to deref metrics we just dumped before doing retirement scan. + const_cast<MetricRegistry*>(this)->RetireOldMetrics(); + return Status::OK(); +} + +void MetricRegistry::RetireOldMetrics() { + std::lock_guard<simple_spinlock> l(lock_); + for (auto it = entities_.begin(); it != entities_.end();) { + it->second->RetireOldMetrics(); + + if (it->second->num_metrics() == 0 && it->second->HasOneRef()) { + // No metrics and no external references to this entity, so we can retire it. + // Unlike retiring the metrics themselves, we don't wait for any timeout + // to retire them -- we assume that that timed retention has been satisfied + // by holding onto the metrics inside the entity. + entities_.erase(it++); + } else { + ++it; + } + } +} + +// +// MetricPrototypeRegistry +// +MetricPrototypeRegistry* MetricPrototypeRegistry::get() { + return Singleton<MetricPrototypeRegistry>::get(); +} + +void MetricPrototypeRegistry::AddMetric(const MetricPrototype* prototype) { + std::lock_guard<simple_spinlock> l(lock_); + metrics_.push_back(prototype); +} + +void MetricPrototypeRegistry::AddEntity(const MetricEntityPrototype* prototype) { + std::lock_guard<simple_spinlock> l(lock_); + entities_.push_back(prototype); +} + +void MetricPrototypeRegistry::WriteAsJson(JsonWriter* writer) const { + std::lock_guard<simple_spinlock> l(lock_); + MetricJsonOptions opts; + opts.include_schema_info = true; + writer->StartObject(); + + // Dump metric prototypes. + writer->String("metrics"); + writer->StartArray(); + for (const MetricPrototype* p : metrics_) { + writer->StartObject(); + p->WriteFields(writer, opts); + writer->String("entity_type"); + writer->String(p->entity_type()); + writer->EndObject(); + } + writer->EndArray(); + + // Dump entity prototypes. + writer->String("entities"); + writer->StartArray(); + for (const MetricEntityPrototype* p : entities_) { + writer->StartObject(); + writer->String("name"); + writer->String(p->name()); + writer->EndObject(); + } + writer->EndArray(); + + writer->EndObject(); +} + +void MetricPrototypeRegistry::WriteAsJsonAndExit() const { + std::ostringstream s; + JsonWriter w(&s, JsonWriter::PRETTY); + WriteAsJson(&w); + std::cout << s.str() << std::endl; + exit(0); +} + +// +// MetricPrototype +// +MetricPrototype::MetricPrototype(CtorArgs args) : args_(std::move(args)) { + MetricPrototypeRegistry::get()->AddMetric(this); +} + +void MetricPrototype::WriteFields(JsonWriter* writer, + const MetricJsonOptions& opts) const { + writer->String("name"); + writer->String(name()); + + if (opts.include_schema_info) { + writer->String("label"); + writer->String(label()); + + writer->String("type"); + writer->String(MetricType::Name(type())); + + writer->String("unit"); + writer->String(MetricUnit::Name(unit())); + + writer->String("description"); + writer->String(description()); + } +} + +// +// FunctionGaugeDetacher +// + +FunctionGaugeDetacher::FunctionGaugeDetacher() { +} + +FunctionGaugeDetacher::~FunctionGaugeDetacher() { + for (const Closure& c : callbacks_) { + c.Run(); + } +} + +scoped_refptr<MetricEntity> MetricRegistry::FindOrCreateEntity( + const MetricEntityPrototype* prototype, + const std::string& id, + const MetricEntity::AttributeMap& initial_attributes) { + std::lock_guard<simple_spinlock> l(lock_); + scoped_refptr<MetricEntity> e = FindPtrOrNull(entities_, id); + if (!e) { + e = new MetricEntity(prototype, id, initial_attributes); + InsertOrDie(&entities_, id, e); + } else { + e->SetAttributes(initial_attributes); + } + return e; +} + +// +// Metric +// +Metric::Metric(const MetricPrototype* prototype) + : prototype_(prototype) { +} + +Metric::~Metric() { +} + +// +// Gauge +// + +Status Gauge::WriteAsJson(JsonWriter* writer, + const MetricJsonOptions& opts) const { + writer->StartObject(); + + prototype_->WriteFields(writer, opts); + + writer->String("value"); + WriteValue(writer); + + writer->EndObject(); + return Status::OK(); +} + +// +// StringGauge +// + +StringGauge::StringGauge(const GaugePrototype<string>* proto, + string initial_value) + : Gauge(proto), value_(std::move(initial_value)) {} + +std::string StringGauge::value() const { + std::lock_guard<simple_spinlock> l(lock_); + return value_; +} + +void StringGauge::set_value(const std::string& value) { + std::lock_guard<simple_spinlock> l(lock_); + value_ = value; +} + +void StringGauge::WriteValue(JsonWriter* writer) const { + writer->String(value()); +} + +// +// Counter +// +// This implementation is optimized by using a striped counter. See LongAdder for details. + +scoped_refptr<Counter> CounterPrototype::Instantiate(const scoped_refptr<MetricEntity>& entity) { + return entity->FindOrCreateCounter(this); +} + +Counter::Counter(const CounterPrototype* proto) : Metric(proto) { +} + +int64_t Counter::value() const { + return value_.Value(); +} + +void Counter::Increment() { + IncrementBy(1); +} + +void Counter::IncrementBy(int64_t amount) { + value_.IncrementBy(amount); +} + +Status Counter::WriteAsJson(JsonWriter* writer, + const MetricJsonOptions& opts) const { + writer->StartObject(); + + prototype_->WriteFields(writer, opts); + + writer->String("value"); + writer->Int64(value()); + + writer->EndObject(); + return Status::OK(); +} + +///////////////////////////////////////////////// +// HistogramPrototype +///////////////////////////////////////////////// + +HistogramPrototype::HistogramPrototype(const MetricPrototype::CtorArgs& args, + uint64_t max_trackable_value, int num_sig_digits) + : MetricPrototype(args), + max_trackable_value_(max_trackable_value), + num_sig_digits_(num_sig_digits) { + // Better to crash at definition time that at instantiation time. + CHECK(HdrHistogram::IsValidHighestTrackableValue(max_trackable_value)) + << Substitute("Invalid max trackable value on histogram $0: $1", + args.name_, max_trackable_value); + CHECK(HdrHistogram::IsValidNumSignificantDigits(num_sig_digits)) + << Substitute("Invalid number of significant digits on histogram $0: $1", + args.name_, num_sig_digits); +} + +scoped_refptr<Histogram> HistogramPrototype::Instantiate( + const scoped_refptr<MetricEntity>& entity) { + return entity->FindOrCreateHistogram(this); +} + +///////////////////////////////////////////////// +// Histogram +///////////////////////////////////////////////// + +Histogram::Histogram(const HistogramPrototype* proto) + : Metric(proto), + histogram_(new HdrHistogram(proto->max_trackable_value(), proto->num_sig_digits())) { +} + +void Histogram::Increment(int64_t value) { + histogram_->Increment(value); +} + +void Histogram::IncrementBy(int64_t value, int64_t amount) { + histogram_->IncrementBy(value, amount); +} + +Status Histogram::WriteAsJson(JsonWriter* writer, + const MetricJsonOptions& opts) const { + + HistogramSnapshotPB snapshot; + RETURN_NOT_OK(GetHistogramSnapshotPB(&snapshot, opts)); + writer->Protobuf(snapshot); + return Status::OK(); +} + +Status Histogram::GetHistogramSnapshotPB(HistogramSnapshotPB* snapshot_pb, + const MetricJsonOptions& opts) const { + HdrHistogram snapshot(*histogram_); + snapshot_pb->set_name(prototype_->name()); + if (opts.include_schema_info) { + snapshot_pb->set_type(MetricType::Name(prototype_->type())); + snapshot_pb->set_label(prototype_->label()); + snapshot_pb->set_unit(MetricUnit::Name(prototype_->unit())); + snapshot_pb->set_description(prototype_->description()); + snapshot_pb->set_max_trackable_value(snapshot.highest_trackable_value()); + snapshot_pb->set_num_significant_digits(snapshot.num_significant_digits()); + } + snapshot_pb->set_total_count(snapshot.TotalCount()); + snapshot_pb->set_total_sum(snapshot.TotalSum()); + snapshot_pb->set_min(snapshot.MinValue()); + snapshot_pb->set_mean(snapshot.MeanValue()); + snapshot_pb->set_percentile_75(snapshot.ValueAtPercentile(75)); + snapshot_pb->set_percentile_95(snapshot.ValueAtPercentile(95)); + snapshot_pb->set_percentile_99(snapshot.ValueAtPercentile(99)); + snapshot_pb->set_percentile_99_9(snapshot.ValueAtPercentile(99.9)); + snapshot_pb->set_percentile_99_99(snapshot.ValueAtPercentile(99.99)); + snapshot_pb->set_max(snapshot.MaxValue()); + + if (opts.include_raw_histograms) { + RecordedValuesIterator iter(&snapshot); + while (iter.HasNext()) { + HistogramIterationValue value; + RETURN_NOT_OK(iter.Next(&value)); + snapshot_pb->add_values(value.value_iterated_to); + snapshot_pb->add_counts(value.count_at_value_iterated_to); + } + } + return Status::OK(); +} + +uint64_t Histogram::CountInBucketForValueForTests(uint64_t value) const { + return histogram_->CountInBucketForValue(value); +} + +uint64_t Histogram::TotalCount() const { + return histogram_->TotalCount(); +} + +uint64_t Histogram::MinValueForTests() const { + return histogram_->MinValue(); +} + +uint64_t Histogram::MaxValueForTests() const { + return histogram_->MaxValue(); +} +double Histogram::MeanValueForTests() const { + return histogram_->MeanValue(); +} + +ScopedLatencyMetric::ScopedLatencyMetric(Histogram* latency_hist) + : latency_hist_(latency_hist) { + if (latency_hist_) { + time_started_ = MonoTime::Now(); + } +} + +ScopedLatencyMetric::~ScopedLatencyMetric() { + if (latency_hist_ != nullptr) { + MonoTime time_now = MonoTime::Now(); + latency_hist_->Increment((time_now - time_started_).ToMicroseconds()); + } +} + +} // namespace kudu http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/d6abb29d/be/src/kudu/util/metrics.h ---------------------------------------------------------------------- diff --git a/be/src/kudu/util/metrics.h b/be/src/kudu/util/metrics.h new file mode 100644 index 0000000..8aeaf93 --- /dev/null +++ b/be/src/kudu/util/metrics.h @@ -0,0 +1,1081 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 KUDU_UTIL_METRICS_H +#define KUDU_UTIL_METRICS_H + +///////////////////////////////////////////////////// +// Kudu Metrics +///////////////////////////////////////////////////// +// +// Summary +// ------------------------------------------------------------ +// +// This API provides a basic set of metrics primitives along the lines of the Coda Hale's +// metrics library along with JSON formatted output of running metrics. +// +// The metrics system has a few main concepts in its data model: +// +// Metric Prototypes +// ----------------- +// Every metric that may be emitted is constructed from a prototype. The prototype defines +// the name of the metric, the entity it is attached to, its type, its units, and a description. +// +// Metric prototypes are defined statically using the METRIC_DEFINE_*(...) macros. This +// allows us to easily enumerate a full list of every metric that might be emitted from a +// server, thus allowing auto-generation of metric metadata for integration with +// monitoring systems such as Cloudera Manager. +// +// Metric Entity Prototypes +// ------------------------ +// The other main type in the data model is the Metric Entity. The most basic entity is the +// "server" entity -- metrics such as memory usage, RPC rates, etc, are typically associated +// with the server as a whole. +// +// Users of the metrics framework can define more entity types using the +// METRIC_DEFINE_entity(...) macro. +// +// MetricEntity instances +// ----------------------- +// Each defined Metric Entity Type serves as a prototype allowing instantiation of a +// MetricEntity object. Each instance then has its own unique set of metrics. For +// example, in the case of Kudu, we define a Metric Entity Type called 'tablet', and the +// Tablet Server instantiates one MetricEntity instance per tablet that it hosts. +// +// MetricEntity instances are instantiated within a MetricRegistry, and each instance is +// expected to have a unique string identifier within that registry. To continue the +// example above, a tablet entity uses its tablet ID as its unique identifier. These +// identifiers are exposed to the operator and surfaced in monitoring tools. +// +// MetricEntity instances may also carry a key-value map of string attributes. These +// attributes are directly exposed to monitoring systems via the JSON output. Monitoring +// systems may use this information to allow hierarchical aggregation beteween entities, +// display them to the user, etc. +// +// Metric instances +// ---------------- +// Given a MetricEntity instance and a Metric Prototype, one can instantiate a Metric +// instance. For example, the Kudu Tablet Server instantiates one MetricEntity instance +// for each tablet, and then instantiates the 'tablet_rows_inserted' prototype within that +// entity. Thus, each tablet then has a separate instance of the metric, allowing the end +// operator to track the metric on a per-tablet basis. +// +// +// Types of metrics +// ------------------------------------------------------------ +// Gauge: Set or get a point-in-time value. +// - string: Gauge for a string value. +// - Primitive types (bool, int64_t/uint64_t, double): Lock-free gauges. +// Counter: Get, reset, increment or decrement an int64_t value. +// Histogram: Increment buckets of values segmented by configurable max and precision. +// +// Gauge vs. Counter +// ------------------------------------------------------------ +// +// A Counter is a metric we expect to only monotonically increase. A +// Gauge is a metric that can decrease and increase. Use a Gauge to +// reflect a sample, e.g., the number of transaction in-flight at a +// given time; use a Counter when considering a metric over time, +// e.g., exposing the number of transactions processed since start to +// produce a metric for the number of transactions processed over some +// time period. +// +// The one exception to this rule is that occasionally it may be more convenient to +// implement a metric as a Gauge, even when it is logically a counter, due to Gauge's +// support for fetching metric values via a bound function. In that case, you can +// use the 'EXPOSE_AS_COUNTER' flag when defining the gauge prototype. For example: +// +// METRIC_DEFINE_gauge_uint64(server, threads_started, +// "Threads Started", +// kudu::MetricUnit::kThreads, +// "Total number of threads started on this server", +// kudu::EXPOSE_AS_COUNTER); +// +// +// Metrics ownership +// ------------------------------------------------------------ +// +// Metrics are reference-counted, and one of the references is always held by a metrics +// entity itself. Users of metrics should typically hold a scoped_refptr to their metrics +// within class instances, so that they also hold a reference. The one exception to this +// is FunctionGauges: see the class documentation below for a typical Gauge ownership pattern. +// +// Because the metrics entity holds a reference to the metric, this means that metrics will +// not be immediately destructed when your class instance publishing them is destructed. +// This is on purpose: metrics are retained for a configurable time interval even after they +// are no longer being published. The purpose of this is to allow monitoring systems, which +// only poll metrics infrequently (eg once a minute) to see the last value of a metric whose +// owner was destructed in between two polls. +// +// +// Example usage for server-level metrics +// ------------------------------------------------------------ +// +// 1) In your server class, define the top-level registry and the server entity: +// +// MetricRegistry metric_registry_; +// scoped_refptr<MetricEntity> metric_entity_; +// +// 2) In your server constructor/initialization, construct metric_entity_. This instance +// will be plumbed through into other subsystems that want to register server-level +// metrics. +// +// metric_entity_ = METRIC_ENTITY_server.Instantiate(®istry_, "some server identifier)"); +// +// 3) At the top of your .cc file where you want to emit a metric, define the metric prototype: +// +// METRIC_DEFINE_counter(server, ping_requests, "Ping Requests", kudu::MetricUnit::kRequests, +// "Number of Ping() RPC requests this server has handled since start"); +// +// 4) In your class where you want to emit metrics, define the metric instance itself: +// scoped_refptr<Counter> ping_counter_; +// +// 5) In your class constructor, instantiate the metric based on the MetricEntity plumbed in: +// +// MyClass(..., const scoped_refptr<MetricEntity>& metric_entity) : +// ping_counter_(METRIC_ping_requests.Instantiate(metric_entity)) { +// } +// +// 6) Where you want to change the metric value, just use the instance variable: +// +// ping_counter_->IncrementBy(100); +// +// +// Example usage for custom entity metrics +// ------------------------------------------------------------ +// Follow the same pattern as above, but also define a metric entity somewhere. For example: +// +// At the top of your CC file: +// +// METRIC_DEFINE_entity(my_entity); +// METRIC_DEFINE_counter(my_entity, ping_requests, "Ping Requests", kudu::MetricUnit::kRequests, +// "Number of Ping() RPC requests this particular entity has handled since start"); +// +// In whatever class represents the entity: +// +// entity_ = METRIC_ENTITY_my_entity.Instantiate(®istry_, my_entity_id); +// +// In whatever classes emit metrics: +// +// scoped_refptr<Counter> ping_requests_ = METRIC_ping_requests.Instantiate(entity); +// ping_requests_->Increment(); +// +// NOTE: at runtime, the metrics system prevents you from instantiating a metric in the +// wrong entity type. This ensures that the metadata can fully describe the set of metric-entity +// relationships. +// +// Plumbing of MetricEntity and MetricRegistry objects +// ------------------------------------------------------------ +// Generally, the rule of thumb to follow when plumbing through entities and registries is +// this: if you're creating new entities or you need to dump the registry contents +// (e.g. path handlers), pass in the registry. Otherwise, pass in the entity. +// +// =========== +// JSON output +// =========== +// +// The first-class output format for metrics is pretty-printed JSON. +// Such a format is relatively easy for humans and machines to read. +// +// The top level JSON object is an array, which contains one element per +// entity. Each entity is an object which has its type, id, and an array +// of metrics. Each metric contains its type, name, unit, description, value, +// etc. +// TODO: Output to HTML. +// +// Example JSON output: +// +// [ +// { +// "type": "tablet", +// "id": "e95e57ba8d4d48458e7c7d35020d4a46", +// "attributes": { +// "table_id": "12345", +// "table_name": "my_table" +// }, +// "metrics": [ +// { +// "type": "counter", +// "name": "log_reader_bytes_read", +// "label": "Log Reader Bytes Read", +// "unit": "bytes", +// "description": "Number of bytes read since tablet start", +// "value": 0 +// }, +// ... +// ] +// }, +// ... +// ] +// +///////////////////////////////////////////////////// + +#include <algorithm> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#include <gtest/gtest_prod.h> + +#include "kudu/gutil/bind.h" +#include "kudu/gutil/callback.h" +#include "kudu/gutil/casts.h" +#include "kudu/gutil/map-util.h" +#include "kudu/gutil/ref_counted.h" +#include "kudu/gutil/singleton.h" +#include "kudu/util/atomic.h" +#include "kudu/util/jsonwriter.h" +#include "kudu/util/locks.h" +#include "kudu/util/monotime.h" +#include "kudu/util/status.h" +#include "kudu/util/striped64.h" + +// Define a new entity type. +// +// The metrics subsystem itself defines the entity type 'server', but other +// entity types can be registered using this macro. +#define METRIC_DEFINE_entity(name) \ + ::kudu::MetricEntityPrototype METRIC_ENTITY_##name(#name) + +// Convenience macros to define metric prototypes. +// See the documentation at the top of this file for example usage. +#define METRIC_DEFINE_counter(entity, name, label, unit, desc) \ + ::kudu::CounterPrototype METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc)) + +#define METRIC_DEFINE_gauge_string(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<std::string> METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) +#define METRIC_DEFINE_gauge_bool(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<bool> METRIC_## name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) +#define METRIC_DEFINE_gauge_int32(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<int32_t> METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) +#define METRIC_DEFINE_gauge_uint32(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<uint32_t> METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) +#define METRIC_DEFINE_gauge_int64(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<int64_t> METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) +#define METRIC_DEFINE_gauge_uint64(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<uint64_t> METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) +#define METRIC_DEFINE_gauge_double(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<double> METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) + +#define METRIC_DEFINE_histogram(entity, name, label, unit, desc, max_val, num_sig_digits) \ + ::kudu::HistogramPrototype METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc), \ + max_val, num_sig_digits) + +// The following macros act as forward declarations for entity types and metric prototypes. +#define METRIC_DECLARE_entity(name) \ + extern ::kudu::MetricEntityPrototype METRIC_ENTITY_##name +#define METRIC_DECLARE_counter(name) \ + extern ::kudu::CounterPrototype METRIC_##name +#define METRIC_DECLARE_gauge_string(name) \ + extern ::kudu::GaugePrototype<std::string> METRIC_##name +#define METRIC_DECLARE_gauge_bool(name) \ + extern ::kudu::GaugePrototype<bool> METRIC_##name +#define METRIC_DECLARE_gauge_int32(name) \ + extern ::kudu::GaugePrototype<int32_t> METRIC_##name +#define METRIC_DECLARE_gauge_uint32(name) \ + extern ::kudu::GaugePrototype<uint32_t> METRIC_##name +#define METRIC_DECLARE_gauge_int64(name) \ + extern ::kudu::GaugePrototype<int64_t> METRIC_##name +#define METRIC_DECLARE_gauge_uint64(name) \ + extern ::kudu::GaugePrototype<uint64_t> METRIC_##name +#define METRIC_DECLARE_gauge_double(name) \ + extern ::kudu::GaugePrototype<double> METRIC_##name +#define METRIC_DECLARE_histogram(name) \ + extern ::kudu::HistogramPrototype METRIC_##name + +#if defined(__APPLE__) +#define METRIC_DEFINE_gauge_size(entity, name, label, unit, desc, ...) \ + ::kudu::GaugePrototype<size_t> METRIC_##name( \ + ::kudu::MetricPrototype::CtorArgs(#entity, #name, label, unit, desc, ## __VA_ARGS__)) +#define METRIC_DECLARE_gauge_size(name) \ + extern ::kudu::GaugePrototype<size_t> METRIC_##name +#else +#define METRIC_DEFINE_gauge_size METRIC_DEFINE_gauge_uint64 +#define METRIC_DECLARE_gauge_size METRIC_DECLARE_gauge_uint64 +#endif + +namespace kudu { + +class Counter; +class CounterPrototype; + +template<typename T> +class AtomicGauge; +template<typename T> +class FunctionGauge; +class Gauge; +template<typename T> +class GaugePrototype; + +class Metric; +class MetricEntityPrototype; +class MetricPrototype; +class MetricRegistry; + +class HdrHistogram; +class Histogram; +class HistogramPrototype; +class HistogramSnapshotPB; + +class MetricEntity; + +} // namespace kudu + +// Forward-declare the generic 'server' entity type. +// We have to do this here below the forward declarations, but not +// in the kudu namespace. +METRIC_DECLARE_entity(server); + +namespace kudu { + +// Unit types to be used with metrics. +// As additional units are required, add them to this enum and also to Name(). +struct MetricUnit { + enum Type { + kCacheHits, + kCacheQueries, + kBytes, + kRequests, + kEntries, + kRows, + kCells, + kConnections, + kOperations, + kProbes, + kNanoseconds, + kMicroseconds, + kMilliseconds, + kSeconds, + kThreads, + kTransactions, + kUnits, + kScanners, + kMaintenanceOperations, + kBlocks, + kLogBlockContainers, + kTasks, + kMessages, + kContextSwitches, + kDataDirectories, + }; + static const char* Name(Type unit); +}; + +class MetricType { + public: + enum Type { kGauge, kCounter, kHistogram }; + static const char* Name(Type t); + private: + static const char* const kGaugeType; + static const char* const kCounterType; + static const char* const kHistogramType; +}; + +struct MetricJsonOptions { + MetricJsonOptions() : + include_raw_histograms(false), + include_schema_info(false) { + } + + // Include the raw histogram values and counts in the JSON output. + // This allows consumers to do cross-server aggregation or window + // data over time. + // Default: false + bool include_raw_histograms; + + // Include the metrics "schema" information (i.e description, label, + // unit, etc). + // Default: false + bool include_schema_info; +}; + +class MetricEntityPrototype { + public: + explicit MetricEntityPrototype(const char* name); + ~MetricEntityPrototype(); + + const char* name() const { return name_; } + + // Find or create an entity with the given ID within the provided 'registry'. + scoped_refptr<MetricEntity> Instantiate( + MetricRegistry* registry, + const std::string& id) const { + return Instantiate(registry, id, std::unordered_map<std::string, std::string>()); + } + + // If the entity already exists, then 'initial_attrs' will replace all existing + // attributes. + scoped_refptr<MetricEntity> Instantiate( + MetricRegistry* registry, + const std::string& id, + const std::unordered_map<std::string, std::string>& initial_attrs) const; + + private: + const char* const name_; + + DISALLOW_COPY_AND_ASSIGN(MetricEntityPrototype); +}; + +class MetricEntity : public RefCountedThreadSafe<MetricEntity> { + public: + typedef std::unordered_map<const MetricPrototype*, scoped_refptr<Metric> > MetricMap; + typedef std::unordered_map<std::string, std::string> AttributeMap; + + scoped_refptr<Counter> FindOrCreateCounter(const CounterPrototype* proto); + scoped_refptr<Histogram> FindOrCreateHistogram(const HistogramPrototype* proto); + + template<typename T> + scoped_refptr<AtomicGauge<T> > FindOrCreateGauge(const GaugePrototype<T>* proto, + const T& initial_value); + + template<typename T> + scoped_refptr<FunctionGauge<T> > FindOrCreateFunctionGauge(const GaugePrototype<T>* proto, + const Callback<T()>& function); + + // Return the metric instantiated from the given prototype, or NULL if none has been + // instantiated. Primarily used by tests trying to read metric values. + scoped_refptr<Metric> FindOrNull(const MetricPrototype& prototype) const; + + const std::string& id() const { return id_; } + + // See MetricRegistry::WriteAsJson() + Status WriteAsJson(JsonWriter* writer, + const std::vector<std::string>& requested_metrics, + const MetricJsonOptions& opts) const; + + const MetricMap& UnsafeMetricsMapForTests() const { return metric_map_; } + + // Mark that the given metric should never be retired until the metric + // registry itself destructs. This is useful for system metrics such as + // tcmalloc, etc, which should live as long as the process itself. + void NeverRetire(const scoped_refptr<Metric>& metric); + + // Scan the metrics map for metrics needing retirement, removing them as necessary. + // + // Metrics are retired when they are no longer referenced outside of the metrics system + // itself. Additionally, we only retire a metric that has been in this state for + // at least FLAGS_metrics_retirement_age_ms milliseconds. + void RetireOldMetrics(); + + // Replaces all attributes for this entity. + // Any attributes currently set, but not in 'attrs', are removed. + void SetAttributes(const AttributeMap& attrs); + + // Set a particular attribute. Replaces any current value. + void SetAttribute(const std::string& key, const std::string& val); + + int num_metrics() const { + std::lock_guard<simple_spinlock> l(lock_); + return metric_map_.size(); + } + + private: + friend class MetricRegistry; + friend class RefCountedThreadSafe<MetricEntity>; + + MetricEntity(const MetricEntityPrototype* prototype, std::string id, + AttributeMap attributes); + ~MetricEntity(); + + // Ensure that the given metric prototype is allowed to be instantiated + // within this entity. This entity's type must match the expected entity + // type defined within the metric prototype. + void CheckInstantiation(const MetricPrototype* proto) const; + + const MetricEntityPrototype* const prototype_; + const std::string id_; + + mutable simple_spinlock lock_; + + // Map from metric name to Metric object. Protected by lock_. + MetricMap metric_map_; + + // The key/value attributes. Protected by lock_ + AttributeMap attributes_; + + // The set of metrics which should never be retired. Protected by lock_. + std::vector<scoped_refptr<Metric> > never_retire_metrics_; +}; + +// Base class to allow for putting all metrics into a single container. +// See documentation at the top of this file for information on metrics ownership. +class Metric : public RefCountedThreadSafe<Metric> { + public: + // All metrics must be able to render themselves as JSON. + virtual Status WriteAsJson(JsonWriter* writer, + const MetricJsonOptions& opts) const = 0; + + const MetricPrototype* prototype() const { return prototype_; } + + protected: + explicit Metric(const MetricPrototype* prototype); + virtual ~Metric(); + + const MetricPrototype* const prototype_; + + private: + friend class MetricEntity; + friend class RefCountedThreadSafe<Metric>; + + // The time at which we should retire this metric if it is still un-referenced outside + // of the metrics subsystem. If this metric is not due for retirement, this member is + // uninitialized. + MonoTime retire_time_; + + DISALLOW_COPY_AND_ASSIGN(Metric); +}; + +// Registry of all the metrics for a server. +// +// This aggregates the MetricEntity objects associated with the server. +class MetricRegistry { + public: + MetricRegistry(); + ~MetricRegistry(); + + scoped_refptr<MetricEntity> FindOrCreateEntity(const MetricEntityPrototype* prototype, + const std::string& id, + const MetricEntity::AttributeMap& initial_attrs); + + // Writes metrics in this registry to 'writer'. + // + // 'requested_metrics' is a set of substrings to match metric names against, + // where '*' matches all metrics. + // + // The string matching can either match an entity ID or a metric name. + // If it matches an entity ID, then all metrics for that entity will be printed. + // + // See the MetricJsonOptions struct definition above for options changing the + // output of this function. + Status WriteAsJson(JsonWriter* writer, + const std::vector<std::string>& requested_metrics, + const MetricJsonOptions& opts) const; + + // For each registered entity, retires orphaned metrics. If an entity has no more + // metrics and there are no external references, entities are removed as well. + // + // See MetricEntity::RetireOldMetrics(). + void RetireOldMetrics(); + + // Return the number of entities in this registry. + int num_entities() const { + std::lock_guard<simple_spinlock> l(lock_); + return entities_.size(); + } + + private: + typedef std::unordered_map<std::string, scoped_refptr<MetricEntity> > EntityMap; + EntityMap entities_; + + mutable simple_spinlock lock_; + DISALLOW_COPY_AND_ASSIGN(MetricRegistry); +}; + +// Registry of all of the metric and entity prototypes that have been +// defined. +// +// Prototypes are typically defined as static variables in different compilation +// units, and their constructors register themselves here. The registry is then +// used in order to dump metrics metadata to generate a Cloudera Manager MDL +// file. +// +// This class is thread-safe. +class MetricPrototypeRegistry { + public: + // Get the singleton instance. + static MetricPrototypeRegistry* get(); + + // Dump a JSON document including all of the registered entity and metric + // prototypes. + void WriteAsJson(JsonWriter* writer) const; + + // Convenience wrapper around WriteAsJson(...). This dumps the JSON information + // to stdout and then exits. + void WriteAsJsonAndExit() const; + private: + friend class Singleton<MetricPrototypeRegistry>; + friend class MetricPrototype; + friend class MetricEntityPrototype; + MetricPrototypeRegistry() {} + ~MetricPrototypeRegistry() {} + + // Register a metric prototype in the registry. + void AddMetric(const MetricPrototype* prototype); + + // Register a metric entity prototype in the registry. + void AddEntity(const MetricEntityPrototype* prototype); + + mutable simple_spinlock lock_; + std::vector<const MetricPrototype*> metrics_; + std::vector<const MetricEntityPrototype*> entities_; + + DISALLOW_COPY_AND_ASSIGN(MetricPrototypeRegistry); +}; + +enum PrototypeFlags { + // Flag which causes a Gauge prototype to expose itself as if it + // were a counter. + EXPOSE_AS_COUNTER = 1 << 0 +}; + +class MetricPrototype { + public: + // Simple struct to aggregate the arguments common to all prototypes. + // This makes constructor chaining a little less tedious. + struct CtorArgs { + CtorArgs(const char* entity_type, + const char* name, + const char* label, + MetricUnit::Type unit, + const char* description, + uint32_t flags = 0) + : entity_type_(entity_type), + name_(name), + label_(label), + unit_(unit), + description_(description), + flags_(flags) { + } + + const char* const entity_type_; + const char* const name_; + const char* const label_; + const MetricUnit::Type unit_; + const char* const description_; + const uint32_t flags_; + }; + + const char* entity_type() const { return args_.entity_type_; } + const char* name() const { return args_.name_; } + const char* label() const { return args_.label_; } + MetricUnit::Type unit() const { return args_.unit_; } + const char* description() const { return args_.description_; } + virtual MetricType::Type type() const = 0; + + // Writes the fields of this prototype to the given JSON writer. + void WriteFields(JsonWriter* writer, + const MetricJsonOptions& opts) const; + + protected: + explicit MetricPrototype(CtorArgs args); + virtual ~MetricPrototype() { + } + + const CtorArgs args_; + + private: + DISALLOW_COPY_AND_ASSIGN(MetricPrototype); +}; + +// A description of a Gauge. +template<typename T> +class GaugePrototype : public MetricPrototype { + public: + explicit GaugePrototype(const MetricPrototype::CtorArgs& args) + : MetricPrototype(args) { + } + + // Instantiate a "manual" gauge. + scoped_refptr<AtomicGauge<T> > Instantiate( + const scoped_refptr<MetricEntity>& entity, + const T& initial_value) const { + return entity->FindOrCreateGauge(this, initial_value); + } + + // Instantiate a gauge that is backed by the given callback. + scoped_refptr<FunctionGauge<T> > InstantiateFunctionGauge( + const scoped_refptr<MetricEntity>& entity, + const Callback<T()>& function) const { + return entity->FindOrCreateFunctionGauge(this, function); + } + + virtual MetricType::Type type() const OVERRIDE { + if (args_.flags_ & EXPOSE_AS_COUNTER) { + return MetricType::kCounter; + } else { + return MetricType::kGauge; + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(GaugePrototype); +}; + +// Abstract base class to provide point-in-time metric values. +class Gauge : public Metric { + public: + explicit Gauge(const MetricPrototype* prototype) + : Metric(prototype) { + } + virtual ~Gauge() {} + virtual Status WriteAsJson(JsonWriter* w, + const MetricJsonOptions& opts) const OVERRIDE; + protected: + virtual void WriteValue(JsonWriter* writer) const = 0; + private: + DISALLOW_COPY_AND_ASSIGN(Gauge); +}; + +// Gauge implementation for string that uses locks to ensure thread safety. +class StringGauge : public Gauge { + public: + StringGauge(const GaugePrototype<std::string>* proto, + std::string initial_value); + std::string value() const; + void set_value(const std::string& value); + protected: + virtual void WriteValue(JsonWriter* writer) const OVERRIDE; + private: + std::string value_; + mutable simple_spinlock lock_; // Guards value_ + DISALLOW_COPY_AND_ASSIGN(StringGauge); +}; + +// Lock-free implementation for types that are convertible to/from int64_t. +template <typename T> +class AtomicGauge : public Gauge { + public: + AtomicGauge(const GaugePrototype<T>* proto, T initial_value) + : Gauge(proto), + value_(initial_value) { + } + T value() const { + return static_cast<T>(value_.Load(kMemOrderRelease)); + } + virtual void set_value(const T& value) { + value_.Store(static_cast<int64_t>(value), kMemOrderNoBarrier); + } + void Increment() { + value_.IncrementBy(1, kMemOrderNoBarrier); + } + virtual void IncrementBy(int64_t amount) { + value_.IncrementBy(amount, kMemOrderNoBarrier); + } + void Decrement() { + IncrementBy(-1); + } + void DecrementBy(int64_t amount) { + IncrementBy(-amount); + } + + protected: + virtual void WriteValue(JsonWriter* writer) const OVERRIDE { + writer->Value(value()); + } + AtomicInt<int64_t> value_; + private: + DISALLOW_COPY_AND_ASSIGN(AtomicGauge); +}; + +// Utility class to automatically detach FunctionGauges when a class destructs. +// +// Because FunctionGauges typically access class instance state, it's important to ensure +// that they are detached before the class destructs. One approach is to make all +// FunctionGauge instances be members of the class, and then call gauge_->Detach() in your +// class's destructor. However, it's easy to forget to do this, which would lead to +// heap-use-after-free bugs. This type of bug is easy to miss in unit tests because the +// tests don't always poll metrics. Using a FunctionGaugeDetacher member instead makes +// the detaching automatic and thus less error-prone. +// +// Example usage: +// +// METRIC_define_gauge_int64(my_metric, MetricUnit::kOperations, "My metric docs"); +// class MyClassWithMetrics { +// public: +// MyClassWithMetrics(const scoped_refptr<MetricEntity>& entity) { +// METRIC_my_metric.InstantiateFunctionGauge(entity, +// Bind(&MyClassWithMetrics::ComputeMyMetric, Unretained(this))) +// ->AutoDetach(&metric_detacher_); +// } +// ~MyClassWithMetrics() { +// } +// +// private: +// int64_t ComputeMyMetric() { +// // Compute some metric based on instance state. +// } +// FunctionGaugeDetacher metric_detacher_; +// }; +class FunctionGaugeDetacher { + public: + FunctionGaugeDetacher(); + ~FunctionGaugeDetacher(); + + private: + template<typename T> + friend class FunctionGauge; + + void OnDestructor(const Closure& c) { + callbacks_.push_back(c); + } + + std::vector<Closure> callbacks_; + + DISALLOW_COPY_AND_ASSIGN(FunctionGaugeDetacher); +}; + + +// A Gauge that calls back to a function to get its value. +// +// This metric type should be used in cases where it is difficult to keep a running +// measure of a metric, but instead would like to compute the metric value whenever it is +// requested by a user. +// +// The lifecycle should be carefully considered when using a FunctionGauge. In particular, +// the bound function needs to always be safe to run -- so if it references a particular +// non-singleton class instance, the instance must out-live the function. Typically, +// the easiest way to ensure this is to use a FunctionGaugeDetacher (see above). +template <typename T> +class FunctionGauge : public Gauge { + public: + T value() const { + std::lock_guard<simple_spinlock> l(lock_); + return function_.Run(); + } + + virtual void WriteValue(JsonWriter* writer) const OVERRIDE { + writer->Value(value()); + } + + // Reset this FunctionGauge to return a specific value. + // This should be used during destruction. If you want a settable + // Gauge, use a normal Gauge instead of a FunctionGauge. + void DetachToConstant(T v) { + std::lock_guard<simple_spinlock> l(lock_); + function_ = Bind(&FunctionGauge::Return, v); + } + + // Get the current value of the gauge, and detach so that it continues to return this + // value in perpetuity. + void DetachToCurrentValue() { + T last_value = value(); + DetachToConstant(last_value); + } + + // Automatically detach this gauge when the given 'detacher' destructs. + // After detaching, the metric will return 'value' in perpetuity. + void AutoDetach(FunctionGaugeDetacher* detacher, T value = T()) { + detacher->OnDestructor(Bind(&FunctionGauge<T>::DetachToConstant, + this, value)); + } + + // Automatically detach this gauge when the given 'detacher' destructs. + // After detaching, the metric will return whatever its value was at the + // time of detaching. + // + // Note that, when using this method, you should be sure that the FunctionGaugeDetacher + // is destructed before any objects which are required by the gauge implementation. + // In typical usage (see the FunctionGaugeDetacher class documentation) this means you + // should declare the detacher member after all other class members that might be + // accessed by the gauge function implementation. + void AutoDetachToLastValue(FunctionGaugeDetacher* detacher) { + detacher->OnDestructor(Bind(&FunctionGauge<T>::DetachToCurrentValue, + this)); + } + + private: + friend class MetricEntity; + + FunctionGauge(const GaugePrototype<T>* proto, Callback<T()> function) + : Gauge(proto), function_(std::move(function)) {} + + static T Return(T v) { + return v; + } + + mutable simple_spinlock lock_; + Callback<T()> function_; + DISALLOW_COPY_AND_ASSIGN(FunctionGauge); +}; + +// Prototype for a counter. +class CounterPrototype : public MetricPrototype { + public: + explicit CounterPrototype(const MetricPrototype::CtorArgs& args) + : MetricPrototype(args) { + } + scoped_refptr<Counter> Instantiate(const scoped_refptr<MetricEntity>& entity); + + virtual MetricType::Type type() const OVERRIDE { return MetricType::kCounter; } + + private: + DISALLOW_COPY_AND_ASSIGN(CounterPrototype); +}; + +// Simple incrementing 64-bit integer. +// Only use Counters in cases that we expect the count to only increase. For example, +// a counter is appropriate for "number of transactions processed by the server", +// but not for "number of transactions currently in flight". Monitoring software +// knows that counters only increase and thus can compute rates over time, rates +// across multiple servers, etc, which aren't appropriate in the case of gauges. +class Counter : public Metric { + public: + int64_t value() const; + void Increment(); + void IncrementBy(int64_t amount); + virtual Status WriteAsJson(JsonWriter* w, + const MetricJsonOptions& opts) const OVERRIDE; + + private: + FRIEND_TEST(MetricsTest, SimpleCounterTest); + FRIEND_TEST(MultiThreadedMetricsTest, CounterIncrementTest); + friend class MetricEntity; + + explicit Counter(const CounterPrototype* proto); + + LongAdder value_; + DISALLOW_COPY_AND_ASSIGN(Counter); +}; + +class HistogramPrototype : public MetricPrototype { + public: + HistogramPrototype(const MetricPrototype::CtorArgs& args, + uint64_t max_trackable_value, int num_sig_digits); + scoped_refptr<Histogram> Instantiate(const scoped_refptr<MetricEntity>& entity); + + uint64_t max_trackable_value() const { return max_trackable_value_; } + int num_sig_digits() const { return num_sig_digits_; } + virtual MetricType::Type type() const OVERRIDE { return MetricType::kHistogram; } + + private: + const uint64_t max_trackable_value_; + const int num_sig_digits_; + DISALLOW_COPY_AND_ASSIGN(HistogramPrototype); +}; + +class Histogram : public Metric { + public: + // Increment the histogram for the given value. + // 'value' must be non-negative. + void Increment(int64_t value); + + // Increment the histogram for the given value by the given amount. + // 'value' and 'amount' must be non-negative. + void IncrementBy(int64_t value, int64_t amount); + + // Return the total number of values added to the histogram (via Increment() + // or IncrementBy()). + uint64_t TotalCount() const; + + virtual Status WriteAsJson(JsonWriter* w, + const MetricJsonOptions& opts) const OVERRIDE; + + // Returns a snapshot of this histogram including the bucketed values and counts. + Status GetHistogramSnapshotPB(HistogramSnapshotPB* snapshot, + const MetricJsonOptions& opts) const; + + uint64_t CountInBucketForValueForTests(uint64_t value) const; + uint64_t MinValueForTests() const; + uint64_t MaxValueForTests() const; + double MeanValueForTests() const; + + private: + FRIEND_TEST(MetricsTest, SimpleHistogramTest); + friend class MetricEntity; + explicit Histogram(const HistogramPrototype* proto); + + const gscoped_ptr<HdrHistogram> histogram_; + DISALLOW_COPY_AND_ASSIGN(Histogram); +}; + +// Measures a duration while in scope. Adds this duration to specified histogram on destruction. +class ScopedLatencyMetric { + public: + // NOTE: the given histogram must live as long as this object. + // If 'latency_hist' is NULL, this turns into a no-op. + explicit ScopedLatencyMetric(Histogram* latency_hist); + ~ScopedLatencyMetric(); + + private: + Histogram* latency_hist_; + MonoTime time_started_; +}; + +#define SCOPED_LATENCY_METRIC(_mtx, _h) \ + ScopedLatencyMetric _h##_metric((_mtx) ? (_mtx)->_h.get() : NULL) + + +//////////////////////////////////////////////////////////// +// Inline implementations of template methods +//////////////////////////////////////////////////////////// + +inline scoped_refptr<Counter> MetricEntity::FindOrCreateCounter( + const CounterPrototype* proto) { + CheckInstantiation(proto); + std::lock_guard<simple_spinlock> l(lock_); + scoped_refptr<Counter> m = down_cast<Counter*>(FindPtrOrNull(metric_map_, proto).get()); + if (!m) { + m = new Counter(proto); + InsertOrDie(&metric_map_, proto, m); + } + return m; +} + +inline scoped_refptr<Histogram> MetricEntity::FindOrCreateHistogram( + const HistogramPrototype* proto) { + CheckInstantiation(proto); + std::lock_guard<simple_spinlock> l(lock_); + scoped_refptr<Histogram> m = down_cast<Histogram*>(FindPtrOrNull(metric_map_, proto).get()); + if (!m) { + m = new Histogram(proto); + InsertOrDie(&metric_map_, proto, m); + } + return m; +} + +template<typename T> +inline scoped_refptr<AtomicGauge<T> > MetricEntity::FindOrCreateGauge( + const GaugePrototype<T>* proto, + const T& initial_value) { + CheckInstantiation(proto); + std::lock_guard<simple_spinlock> l(lock_); + scoped_refptr<AtomicGauge<T> > m = down_cast<AtomicGauge<T>*>( + FindPtrOrNull(metric_map_, proto).get()); + if (!m) { + m = new AtomicGauge<T>(proto, initial_value); + InsertOrDie(&metric_map_, proto, m); + } + return m; +} + +template<typename T> +inline scoped_refptr<FunctionGauge<T> > MetricEntity::FindOrCreateFunctionGauge( + const GaugePrototype<T>* proto, + const Callback<T()>& function) { + CheckInstantiation(proto); + std::lock_guard<simple_spinlock> l(lock_); + scoped_refptr<FunctionGauge<T> > m = down_cast<FunctionGauge<T>*>( + FindPtrOrNull(metric_map_, proto).get()); + if (!m) { + m = new FunctionGauge<T>(proto, function); + InsertOrDie(&metric_map_, proto, m); + } + return m; +} + +} // namespace kudu + +#endif // KUDU_UTIL_METRICS_H http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/d6abb29d/be/src/kudu/util/minidump-test.cc ---------------------------------------------------------------------- diff --git a/be/src/kudu/util/minidump-test.cc b/be/src/kudu/util/minidump-test.cc new file mode 100644 index 0000000..f4c44e2 --- /dev/null +++ b/be/src/kudu/util/minidump-test.cc @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 <signal.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include <gflags/gflags.h> + +#include "kudu/gutil/strings/substitute.h" +#include "kudu/util/minidump.h" +#include "kudu/util/path_util.h" +#include "kudu/util/test_macros.h" +#include "kudu/util/test_util.h" + +using std::string; +using std::vector; + +DECLARE_bool(enable_minidumps); +DECLARE_int32(max_minidumps); +DECLARE_string(minidump_path); + +namespace kudu { + +class MinidumpDeathTest : public KuduTest { + protected: + void WaitForMinidumps(int expected, const string& dir); +}; + +void MinidumpDeathTest::WaitForMinidumps(int expected, const string& dir) { + ASSERT_EVENTUALLY([&] { + vector<string> matches; + ASSERT_OK(env_->Glob(JoinPathSegments(dir, "*.dmp"), &matches)); + ASSERT_EQ(expected, matches.size()); + }); +} + +// Test that registering the minidump exception handler results in creation of +// minidump files on crash. Also test that deleting excess minidump files works +// as expected. +TEST_F(MinidumpDeathTest, TestRegisterAndDelete) { + FLAGS_enable_minidumps = true; + FLAGS_minidump_path = JoinPathSegments(test_dir_, "minidumps"); + MinidumpExceptionHandler minidump_handler; + ASSERT_DEATH({ + abort(); + }, + // Ensure that a stack trace is produced. + "kudu::MinidumpDeathTest_TestRegisterAndDelete_Test::TestBody()"); + + // Ensure that a minidump is produced. + string minidump_dir = minidump_handler.minidump_dir(); + NO_FATALS(WaitForMinidumps(1, minidump_dir)); + + // Now create more minidumps so we can clean them up. + for (int num_dumps : {2, 3}) { + kill(getpid(), SIGUSR1); + NO_FATALS(WaitForMinidumps(num_dumps, minidump_dir)); + } + + FLAGS_max_minidumps = 2; + ASSERT_OK(minidump_handler.DeleteExcessMinidumpFiles(env_)); + NO_FATALS(WaitForMinidumps(2, minidump_dir)); +} + +// Test that a CHECK() failure produces a stack trace and a minidump. +TEST_F(MinidumpDeathTest, TestCheckStackTraceAndMinidump) { + FLAGS_enable_minidumps = true; + FLAGS_minidump_path = JoinPathSegments(test_dir_, "minidumps"); + MinidumpExceptionHandler minidump_handler; + ASSERT_DEATH({ + CHECK_EQ(1, 0); + }, + // Ensure that a stack trace is produced. + "kudu::MinidumpDeathTest_TestCheckStackTraceAndMinidump_Test::TestBody()"); + + // Ensure that a minidump is produced. + string minidump_dir = minidump_handler.minidump_dir(); + NO_FATALS(WaitForMinidumps(1, minidump_dir)); +} + +class MinidumpSignalDeathTest : public MinidumpDeathTest, + public ::testing::WithParamInterface<int> { +}; + +// Test that we get both a minidump and a stack trace for each supported signal. +TEST_P(MinidumpSignalDeathTest, TestHaveMinidumpAndStackTrace) { + FLAGS_enable_minidumps = true; + int signal = GetParam(); + +#if defined(ADDRESS_SANITIZER) + // ASAN appears to catch SIGBUS, SIGSEGV, and SIGFPE and the process is not killed. + if (signal == SIGBUS || signal == SIGSEGV || signal == SIGFPE) { + return; + } +#endif + +#if defined(THREAD_SANITIZER) + // TSAN appears to catch SIGTERM and the process is not killed. + if (signal == SIGTERM) { + return; + } +#endif + + LOG(INFO) << "Testing signal: " << strsignal(signal); + + FLAGS_minidump_path = JoinPathSegments(test_dir_, "minidumps"); + MinidumpExceptionHandler minidump_handler; + ASSERT_DEATH({ + kill(getpid(), signal); + }, + // Ensure that a stack trace is produced. + "kudu::MinidumpSignalDeathTest_TestHaveMinidumpAndStackTrace_Test::TestBody()"); + + // Ensure that a mindump is produced, unless it's SIGTERM, which does not + // create a minidump. + int num_expected_minidumps = 1; + if (signal == SIGTERM) { + num_expected_minidumps = 0; + } + NO_FATALS(WaitForMinidumps(num_expected_minidumps, minidump_handler.minidump_dir())); +} + +INSTANTIATE_TEST_CASE_P(DeadlySignals, MinidumpSignalDeathTest, + ::testing::Values(SIGABRT, SIGBUS, SIGSEGV, SIGILL, SIGFPE, SIGTERM)); + +} // namespace kudu
