blob: cc285c9efe0ac38c6ff99f68d6e9a7836e749329 [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// 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 COBALT_BASE_C_VAL_H_
#define COBALT_BASE_C_VAL_H_
#include <stdint.h>
#include <iomanip>
#include <limits>
#include <set>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
#include "base/compiler_specific.h"
#include "base/containers/hash_tables.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/optional.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "cobalt/base/ref_counted_lock.h"
#include "cobalt/base/type_id.h"
// The CVal system allows you to mark certain variables to be part of the
// CVal system and therefore analyzable and trackable by other systems. All
// modifications to marked variables will trigger an event within a CValManager
// class, which will in turn trigger an event in all attached hooks (e.g. memory
// logging systems, tracing systems, etc...).
// Each marked variable is known as a CVal, and every CVal has an associated
// name so that it can be looked up at run time. Thus, you can only ever
// have one CVal of the same name existing at a time.
//
// CVal's take a Visibility template parameter. There are two valid options for
// it, CValDebug and CValPublic. CValDebug is the default value. They are
// identical when ENABLE_DEBUG_C_VAL is defined; however, when
// ENABLE_DEBUG_C_VAL is not defined, CVals with Visibility set to CValPublic
// remain trackable through the CVal system, while CVals with Visibility set to
// CValDebug stop being trackable and behave as similarly as possible to its
// underlying type.
// You can mark a variable:
//
// int memory_counter;
//
// as a CVal by wrapping the type with the CVal template type:
//
// CVal<int> memory_counter("Memory Counter", 0, "My memory counter");
// CVal<int, CValDebug> memory_counter("Memory Counter", 0,
// "My memory counter");
// CVal<int, CValPublic> memory_counter("Memory Counter", 0,
// "My memory counter");
//
// The first constructor parameter is the name of the CVal, the second is the
// initial value for the variable, and the third is the description.
//
// For the system to function, the singleton CValManager class must be
// instantiated. If a CVal is created before a CValManager is instantiated, it
// will attach itself to the CValManager the next time it is modified. Thus,
// global CVal variables can be supported, but they will not be tracked until
// they are modified for the first time after the CValManager is created. At
// this point, while CVals will be tracked, you will only be notified of changes
// to CVals.
//
// To hook in to the CValManager in order to receive CVal changed notifications,
// you should create a class that subclasses CValManager::OnChangedHook and
// implements the OnValueChanged virtual method. When this class is
// instantiated, it will automatically hook in to the singleton CValManager
// object, which must exist at that point.
//
// NOTE: When the debug console is disabled, change events are never tracked.
namespace base {
namespace CValDetail {
class CValBase;
template <typename T>
class CValImpl;
} // namespace CValDetail
// CVals are commonly used for values that are in units of bytes. By making
// a CVal of type SizeInBytes, this can be made explicit, and allows the CVal
// system to use KB/MB suffixes instead of K/M.
namespace cval {
class SizeInBytes {
public:
SizeInBytes(uint64 size_in_bytes) // NOLINT(runtime/explicit)
: size_in_bytes_(size_in_bytes) {}
SizeInBytes& operator=(uint64 rhs) {
size_in_bytes_ = rhs;
return *this;
}
SizeInBytes& operator+=(const SizeInBytes& rhs) {
size_in_bytes_ += rhs.size_in_bytes_;
return *this;
}
SizeInBytes& operator-=(const SizeInBytes& rhs) {
size_in_bytes_ -= rhs.size_in_bytes_;
return *this;
}
operator uint64() const { return value(); }
uint64 value() const { return size_in_bytes_; }
private:
uint64 size_in_bytes_;
};
inline std::ostream& operator<<(std::ostream& out, const SizeInBytes& size) {
return out << static_cast<uint64>(size);
}
} // namespace cval
namespace CValDetail {
// Provide methods to convert from an arbitrary type to a string, useful for
// systems that want to read the value of a CVal without caring about its type.
template <typename T>
std::string ValToString(const T& value) {
std::ostringstream oss;
oss << value;
return oss.str();
}
template <>
inline std::string ValToString<bool>(const bool& value) {
return value ? "true" : "false";
}
template <>
inline std::string ValToString<std::string>(const std::string& value) {
return value;
}
template <>
inline std::string ValToString<base::TimeDelta>(const base::TimeDelta& value) {
return ValToString(value.InMicroseconds());
}
// Helper function to implement the numerical branch of ValToPrettyString
template <typename T>
struct ThresholdListElement {
T threshold;
T divide_by;
const char* postfix;
};
template <>
struct ThresholdListElement<cval::SizeInBytes> {
uint64 threshold;
uint64 divide_by;
const char* postfix;
};
template <typename T>
struct ThresholdList {
ThresholdList(ThresholdListElement<T>* array, size_t size)
: array(array), size(size) {}
ThresholdListElement<T>* array;
size_t size;
};
template <typename T>
ThresholdList<T> GetThresholdList() {
static ThresholdListElement<T> thresholds[] = {
{static_cast<T>(10 * 1000 * 1000), static_cast<T>(1000 * 1000), "M"},
{static_cast<T>(10 * 1000), static_cast<T>(1000), "K"},
};
return ThresholdList<T>(thresholds, arraysize(thresholds));
}
template <>
inline ThresholdList<cval::SizeInBytes> GetThresholdList<cval::SizeInBytes>() {
static ThresholdListElement<cval::SizeInBytes> thresholds[] = {
{10LL * 1024LL * 1024LL * 1024LL, 1024LL * 1024LL * 1024LL, "GB"},
{10LL * 1024LL * 1024LL, 1024LL * 1024LL, "MB"},
{10LL * 1024LL, 1024LL, "KB"},
{0LL, 1L, "B"},
};
return ThresholdList<cval::SizeInBytes>(thresholds, arraysize(thresholds));
}
template <typename T>
std::string NumericalValToPrettyString(const T& value) {
ThresholdList<T> threshold_list = GetThresholdList<T>();
ThresholdListElement<T>* thresholds = threshold_list.array;
T divided_value = value;
const char* postfix = "";
for (size_t i = 0; i < threshold_list.size; ++i) {
if (value >= thresholds[i].threshold) {
divided_value = value / thresholds[i].divide_by;
postfix = thresholds[i].postfix;
break;
}
}
std::ostringstream oss;
oss << divided_value << postfix;
return oss.str();
}
template <>
inline std::string NumericalValToPrettyString<base::TimeDelta>(
const base::TimeDelta& value) {
const int64 kMicrosecond = 1LL;
const int64 kMillisecond = 1000LL * kMicrosecond;
const int64 kSecond = 1000LL * kMillisecond;
const int64 kMinute = 60LL * kSecond;
const int64 kHour = 60LL * kMinute;
int64 value_in_us = value.InMicroseconds();
bool negative = value_in_us < 0;
value_in_us *= negative ? -1 : 1;
std::ostringstream oss;
if (negative) {
oss << "-";
}
oss << std::fixed << std::setprecision(1) << std::setfill('0');
if (value_in_us > kHour) {
oss << value_in_us / kHour << ":" << std::setw(2)
<< (value_in_us % kHour) / kMinute << ":"
<< std::setw(2) << (value_in_us % kMinute) / kSecond << "h";
} else if (value_in_us > kMinute) {
oss << value_in_us / kMinute << ":" << std::setw(2)
<< (value_in_us % kMinute) / kSecond << "m";
} else if (value_in_us > kSecond) {
oss << std::setw(1)
<< static_cast<double>(value_in_us) / kSecond << "s";
} else if (value_in_us > kMillisecond) {
oss << std::setw(1)
<< static_cast<double>(value_in_us) / kMillisecond << "ms";
} else {
oss << value_in_us << "us";
}
return oss.str();
}
// Helper function for the subsequent ValToPrettyString function.
template <typename T, bool IsNumerical>
class ValToPrettyStringHelper {};
template <typename T>
class ValToPrettyStringHelper<T, true> {
public:
static std::string Call(const T& value) {
return NumericalValToPrettyString(value);
}
};
template <typename T>
class ValToPrettyStringHelper<T, false> {
public:
static std::string Call(const T& value) { return ValToString(value); }
};
template <typename T>
struct IsNumerical {
static const bool value =
(std::is_arithmetic<T>::value && !std::is_same<T, bool>::value) ||
std::is_same<T, base::TimeDelta>::value ||
std::is_same<T, cval::SizeInBytes>::value;
};
// As opposed to ValToString, here we take the opportunity to clean up the
// number a bit to make it easier for humans to digest. For example, if a
// number is much larger than one million, we can divide it by one million
// and postfix it with an 'M'.
template <typename T>
std::string ValToPrettyString(const T& value) {
return ValToPrettyStringHelper<T, IsNumerical<T>::value>::Call(value);
}
// Provides methods for converting the type to and from a double.
template <typename T>
double ToDouble(T value) {
return static_cast<double>(value);
}
template <>
inline double ToDouble<base::TimeDelta>(base::TimeDelta value) {
return static_cast<double>(value.InMicroseconds());
}
template <typename T>
T FromDouble(double value) {
return static_cast<T>(value);
}
template <>
inline base::TimeDelta FromDouble<base::TimeDelta>(double value) {
return base::TimeDelta::FromMicroseconds(static_cast<int64>(value));
}
// Provides methods for retrieving the max and min value for the type.
template <typename T>
T Max() {
return std::numeric_limits<T>::max();
}
template <>
inline base::TimeDelta Max<base::TimeDelta>() {
return base::TimeDelta::Max();
}
template <typename T>
T Min() {
return std::numeric_limits<T>::min();
}
template <>
inline base::TimeDelta Min<base::TimeDelta>() {
return -base::TimeDelta::Max();
}
} // namespace CValDetail
#if defined(ENABLE_DEBUG_C_VAL)
// This class is passed back to the CVal modified event handlers so that they
// can examine the new CVal value. This class knows its type, which can be
// checked through GetType, and then the actual value can be retrieved by
// calling CValGenericValue::AsNativeType<TYPE>() with the correct type. If you
// don't care about the type, you can call CValGenericValue::AsString() and
// retrieve the string version of the value.
class CValGenericValue {
public:
TypeId GetTypeId() const { return type_id_; }
// Return the value casted to the specified type. The requested type must
// match the contained value's actual native type.
template <typename T>
const T& AsNativeType() const {
DCHECK(IsNativeType<T>());
return *static_cast<T*>(generic_value_);
}
// Returns true if the type of this value is exactly T.
template <typename T>
bool IsNativeType() const {
return type_id_ == base::GetTypeId<T>();
}
virtual std::string AsString() const = 0;
virtual std::string AsPrettyString() const = 0;
protected:
CValGenericValue(TypeId type_id, void* value_mem)
: type_id_(type_id), generic_value_(value_mem) {}
virtual ~CValGenericValue() {}
private:
TypeId type_id_;
void* generic_value_;
};
namespace CValDetail {
// Internally used to store the actual typed value that a CValGenericValue may
// contain. This value actually stores the data as a copy and then passes a
// pointer (as a void*) to its parent class CValGenericValue so that the value
// can be accessed from the parent class. This class is intended to snapshot a
// CVal value before passing it along to event handlers ensuring that they all
// receive the same value.
template <typename T>
class CValSpecificValue : public CValGenericValue {
public:
explicit CValSpecificValue(const T& value)
: CValGenericValue(base::GetTypeId<T>(), &value_), value_(value) {}
CValSpecificValue(const CValSpecificValue<T>& other)
: CValGenericValue(base::GetTypeId<T>(), &value_), value_(other.value_) {}
virtual ~CValSpecificValue() {}
std::string AsString() const { return ValToString(value_); }
std::string AsPrettyString() const { return ValToPrettyString(value_); }
private:
T value_;
};
} // namespace CValDetail
#endif // ENABLE_DEBUG_C_VAL
// Manager class required for the CVal tracking system to function.
// This class is designed to be a singleton, instanced only through the methods
// of the base::Singleton class. For example,
// CValManager::GetInstance()->GetValueAsPrettyString(...)
class CValManager {
public:
// Method to get the singleton instance of this class.
static CValManager* GetInstance();
#if defined(ENABLE_DEBUG_C_VAL)
// In order for a system to receive notifications when a tracked CVal changes,
// it should create a subclass of OnChangedHook with OnValueChanged. When
// instantiated, OnChangedHook will be called whenever a CVal changes.
class OnChangedHook {
public:
OnChangedHook();
virtual ~OnChangedHook();
virtual void OnValueChanged(const std::string& name,
const CValGenericValue& value) = 0;
};
#endif // ENABLE_DEBUG_C_VAL
friend struct base::StaticMemorySingletonTraits<CValManager>;
// Returns a set of the sorted names of all CVals currently registered.
std::set<std::string> GetOrderedCValNames();
// Returns the value of the given CVal as a string. The return value is a
// pair, the first element is true if the CVal exists, in which case the
// second element is the value (as a string). If the CVal does not exist,
// the value of the second element is undefined.
Optional<std::string> GetValueAsString(const std::string& name);
// Similar to the above, but formatting may be done on numerical values.
// For example, large numbers may have metric postfixes appended to them.
// i.e. 104857600 = 100M
Optional<std::string> GetValueAsPrettyString(const std::string& name);
private:
// Class can only be instanced via the singleton
CValManager();
~CValManager();
// Called in CVal constructors to register/deregister themselves with the
// system. Registering a CVal will also provide that CVal with a value lock
// to lock when it modifies its value.
void RegisterCVal(const CValDetail::CValBase* cval,
scoped_refptr<base::RefCountedThreadSafeLock>* value_lock);
void UnregisterCVal(const CValDetail::CValBase* cval);
#if defined(ENABLE_DEBUG_C_VAL)
// Called whenever a CVal is modified, and does the work of notifying all
// hooks.
void PushValueChangedEvent(const CValDetail::CValBase* cval,
const CValGenericValue& value);
#endif // ENABLE_DEBUG_C_VAL
// Helper function to remove code duplication between GetValueAsString
// and GetValueAsPrettyString.
Optional<std::string> GetCValStringValue(const std::string& name,
bool pretty);
#if defined(ENABLE_DEBUG_C_VAL)
// Lock that protects against changes to hooks.
base::Lock hooks_lock_;
#endif // ENABLE_DEBUG_C_VAL
// Lock that protects against CVals being registered/deregistered.
base::Lock cvals_lock_;
// Lock that synchronizes |value_| reads/writes. It exists as a refptr to
// ensure that CVals that survive longer than the CValManager can continue
// to use it after the CValManager has been destroyed.
scoped_refptr<base::RefCountedThreadSafeLock> value_lock_refptr_;
// The actual value registry, mapping CVal name to actual CVal object.
typedef base::hash_map<std::string, const CValDetail::CValBase*> NameVarMap;
NameVarMap* registered_vars_;
#if defined(ENABLE_DEBUG_C_VAL)
// The set of hooks that we should notify whenever a CVal is modified.
typedef base::hash_set<OnChangedHook*> OnChangeHookSet;
OnChangeHookSet* on_changed_hook_set_;
#endif // ENABLE_DEBUG_C_VAL
template <typename T>
friend class CValDetail::CValImpl;
DISALLOW_COPY_AND_ASSIGN(CValManager);
};
namespace CValDetail {
class CValBase {
public:
CValBase(const std::string& name, TypeId type_id,
const std::string& description)
: name_(name), description_(description), type_id_(type_id) {}
const std::string& GetName() const { return name_; }
const std::string& GetDescription() const { return description_; }
TypeId GetTypeId() const { return type_id_; }
virtual std::string GetValueAsString() const = 0;
virtual std::string GetValueAsPrettyString() const = 0;
private:
virtual void OnManagerDestroyed() const {}
std::string name_;
std::string description_;
TypeId type_id_;
friend CValManager;
};
// This is a wrapper class that marks that we wish to track a value through
// the CVal system.
template <typename T>
class CValImpl : public CValBase {
public:
CValImpl(const std::string& name, const T& initial_value,
const std::string& description)
: CValBase(name, base::GetTypeId<T>(), description),
value_(initial_value) {
CommonConstructor();
}
CValImpl(const std::string& name, const std::string& description)
: CValBase(name, base::GetTypeId<T>(), description) {
CommonConstructor();
}
virtual ~CValImpl() {
if (registered_) {
CValManager::GetInstance()->UnregisterCVal(this);
}
}
operator T() const { return value(); }
const CValImpl<T>& operator=(const T& rhs) {
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
bool value_changed = value_ != rhs;
if (value_changed) {
value_ = rhs;
#if defined(ENABLE_DEBUG_C_VAL)
OnValueChanged();
#endif // ENABLE_DEBUG_C_VAL
}
return *this;
}
const CValImpl<T>& operator+=(const T& rhs) {
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
value_ += rhs;
#if defined(ENABLE_DEBUG_C_VAL)
OnValueChanged();
#endif // ENABLE_DEBUG_C_VAL
return *this;
}
const CValImpl<T>& operator-=(const T& rhs) {
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
value_ -= rhs;
#if defined(ENABLE_DEBUG_C_VAL)
OnValueChanged();
#endif // ENABLE_DEBUG_C_VAL
return *this;
}
const CValImpl<T>& operator++() {
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
++value_;
#if defined(ENABLE_DEBUG_C_VAL)
OnValueChanged();
#endif // ENABLE_DEBUG_C_VAL
return *this;
}
const CValImpl<T>& operator--() {
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
--value_;
#if defined(ENABLE_DEBUG_C_VAL)
OnValueChanged();
#endif // ENABLE_DEBUG_C_VAL
return *this;
}
bool operator<(const T& rhs) const { return value() < rhs; }
bool operator>(const T& rhs) const { return value() > rhs; }
bool operator==(const T& rhs) const { return value() == rhs; }
std::string GetValueAsString() const {
// Can be called to get the value of a CVal without knowing the type first.
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
return ValToString<T>(value_);
}
std::string GetValueAsPrettyString() const {
// Similar to GetValueAsString(), but it will also format the string to
// do things like make very large numbers more readable.
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
return ValToPrettyString<T>(value_);
}
T value() const {
base::AutoLock auto_lock(value_lock_refptr_->GetLock());
return value_;
}
private:
void CommonConstructor() {
registered_ = false;
RegisterWithManager();
}
void RegisterWithManager() {
if (!registered_) {
CValManager* manager = CValManager::GetInstance();
manager->RegisterCVal(this, &value_lock_refptr_);
registered_ = true;
}
}
void OnManagerDestroyed() const {
// The value lock is locked by the manager prior to it calling
// OnManagerDestroyed on its surviving CVals.
registered_ = false;
}
#if defined(ENABLE_DEBUG_C_VAL)
void OnValueChanged() {
// Push the value changed event to all listeners.
if (registered_) {
CValManager::GetInstance()->PushValueChangedEvent(
this, CValSpecificValue<T>(value_));
}
}
#endif // ENABLE_DEBUG_C_VAL
T value_;
mutable bool registered_;
mutable scoped_refptr<RefCountedThreadSafeLock> value_lock_refptr_;
};
#if !defined(ENABLE_DEBUG_C_VAL)
// This is a wrapper class that mimics the behavior of the underlying type. It
// does not register with the |CValManager| and does not push value changed
// events.
template <typename T>
class CValStub {
public:
CValStub(const std::string& name, const T& initial_value,
const std::string& description)
: value_(initial_value) {
}
CValStub(const std::string& name, const std::string& description) {
}
operator T() const { return value_; }
const CValStub<T>& operator=(const T& rhs) {
value_ = rhs;
return *this;
}
const CValStub<T>& operator+=(const T& rhs) {
value_ += rhs;
return *this;
}
const CValStub<T>& operator-=(const T& rhs) {
value_ -= rhs;
return *this;
}
const CValStub<T>& operator++() {
++value_;
return *this;
}
const CValStub<T>& operator--() {
--value_;
return *this;
}
bool operator<(const T& rhs) const { return value_ < rhs; }
bool operator>(const T& rhs) const { return value_ > rhs; }
bool operator==(const T& rhs) const { return value_ == rhs; }
T value() const { return value_; }
private:
T value_;
};
#endif // ENABLE_DEBUG_C_VAL
} // namespace CValDetail
class CValPublic {};
class CValDebug {};
template <typename T, typename Visibility = CValDebug>
class CVal {};
template <typename T>
#if defined(ENABLE_DEBUG_C_VAL)
// When ENABLE_DEBUG_C_VAL is defined, CVals with Visibility set to CValDebug
// are tracked through the CVal system.
class CVal<T, CValDebug> : public CValDetail::CValImpl<T> {
typedef CValDetail::CValImpl<T> CValParent;
#else // ENABLE_DEBUG_C_VAL
// When ENABLE_DEBUG_C_VAL is not defined, CVals with Visibility set to
// CValDebug are not tracked though the CVal system, and will behave as
// similarly as possible to their underlying types.
class CVal<T, CValDebug> : public CValDetail::CValStub<T> {
typedef CValDetail::CValStub<T> CValParent;
#endif // ENABLE_DEBUG_C_VAL
public:
CVal(const std::string& name, const T& initial_value,
const std::string& description)
: CValParent(name, initial_value, description) {}
CVal(const std::string& name, const std::string& description)
: CValParent(name, description) {}
const CVal& operator=(const T& rhs) {
CValParent::operator=(rhs);
return *this;
}
};
// CVals with visibility set to CValPublic are always tracked though the CVal
// system, regardless of the ENABLE_DEBUG_C_VAL state.
template <typename T>
class CVal<T, CValPublic> : public CValDetail::CValImpl<T> {
typedef CValDetail::CValImpl<T> CValParent;
public:
CVal(const std::string& name, const T& initial_value,
const std::string& description)
: CValParent(name, initial_value, description) {}
CVal(const std::string& name, const std::string& description)
: CValParent(name, description) {}
const CVal& operator=(const T& rhs) {
CValParent::operator=(rhs);
return *this;
}
};
} // namespace base
#endif // COBALT_BASE_C_VAL_H_