family.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #pragma once
  2. #include <cstddef>
  3. #include <map>
  4. #include <memory>
  5. #include <mutex>
  6. #include <string>
  7. #include <unordered_map>
  8. #include <vector>
  9. #include <cassert>
  10. #include <stdexcept>
  11. #include "prometheus/collectable.h"
  12. #include "prometheus/metric.h"
  13. #include "prometheus/hash.h"
  14. namespace prometheus {
  15. /// \brief A metric of type T with a set of labeled dimensions.
  16. ///
  17. /// One of Prometheus main feature is a multi-dimensional data model with time
  18. /// series data identified by metric name and key/value pairs, also known as
  19. /// labels. A time series is a series of data points indexed (or listed or
  20. /// graphed) in time order (https://en.wikipedia.org/wiki/Time_series).
  21. ///
  22. /// An instance of this class is exposed as multiple time series during
  23. /// scrape, i.e., one time series for each set of labels provided to Add().
  24. ///
  25. /// For example it is possible to collect data for a metric
  26. /// `http_requests_total`, with two time series:
  27. ///
  28. /// - all HTTP requests that used the method POST
  29. /// - all HTTP requests that used the method GET
  30. ///
  31. /// The metric name specifies the general feature of a system that is
  32. /// measured, e.g., `http_requests_total`. Labels enable Prometheus's
  33. /// dimensional data model: any given combination of labels for the same
  34. /// metric name identifies a particular dimensional instantiation of that
  35. /// metric. For example a label for 'all HTTP requests that used the method
  36. /// POST' can be assigned with `method= "POST"`.
  37. ///
  38. /// Given a metric name and a set of labels, time series are frequently
  39. /// identified using this notation:
  40. ///
  41. /// <metric name> { < label name >= <label value>, ... }
  42. ///
  43. /// It is required to follow the syntax of metric names and labels given by:
  44. /// https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
  45. ///
  46. /// The following metric and label conventions are not required for using
  47. /// Prometheus, but can serve as both a style-guide and a collection of best
  48. /// practices: https://prometheus.io/docs/practices/naming/
  49. ///
  50. /// tparam T One of the metric types Counter, Gauge, Histogram or Summary.
  51. class Family : public Collectable {
  52. public:
  53. using Hash = std::size_t;
  54. using Label = std::pair<const std::string, const std::string>;
  55. using Labels = std::map <const std::string, const std::string>;
  56. using MetricPtr = std::unique_ptr<Metric>;
  57. const Metric::Type type;
  58. const std::string name;
  59. const std::string help;
  60. const Labels constant_labels;
  61. mutable std::mutex mutex;
  62. std::unordered_map<Hash, MetricPtr> metrics;
  63. std::unordered_map<Hash, Labels> labels;
  64. std::unordered_map<Metric*, Hash> labels_reverse_lookup;
  65. /// \brief Compute the hash value of a map of labels.
  66. ///
  67. /// \param labels The map that will be computed the hash value.
  68. ///
  69. /// \returns The hash value of the given labels.
  70. static Hash hash_labels (const Labels& labels) {
  71. size_t seed = 0;
  72. for (const Label& label : labels)
  73. detail::hash_combine (&seed, label.first, label.second);
  74. return seed;
  75. }
  76. static bool isLocaleIndependentDigit (char c) { return '0' <= c && c <= '9'; }
  77. static bool isLocaleIndependentAlphaNumeric (char c) { return isLocaleIndependentDigit(c) || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); }
  78. bool nameStartsValid (const std::string& name) {
  79. if (name.empty()) return false; // must not be empty
  80. if (isLocaleIndependentDigit(name.front())) return false; // must not start with a digit
  81. if (name.compare(0, 2, "__") == 0) return false; // must not start with "__"
  82. return true;
  83. }
  84. /// \brief Check if the metric name is valid
  85. ///
  86. /// The metric name regex is "[a-zA-Z_:][a-zA-Z0-9_:]*"
  87. ///
  88. /// \see https://prometheus.io/docs/concepts/data_model/
  89. ///
  90. /// \param name metric name
  91. /// \return true is valid, false otherwise
  92. bool CheckMetricName (const std::string& name) {
  93. if (!nameStartsValid(name))
  94. return false;
  95. for (const char& c : name)
  96. if ( !isLocaleIndependentAlphaNumeric(c) && c != '_' && c != ':' )
  97. return false;
  98. return true;
  99. }
  100. /// \brief Check if the label name is valid
  101. ///
  102. /// The label name regex is "[a-zA-Z_][a-zA-Z0-9_]*"
  103. ///
  104. /// \see https://prometheus.io/docs/concepts/data_model/
  105. ///
  106. /// \param name label name
  107. /// \return true is valid, false otherwise
  108. bool CheckLabelName (const std::string& name) {
  109. if (!nameStartsValid(name))
  110. return false;
  111. for (const char& c : name)
  112. if (!isLocaleIndependentAlphaNumeric(c) && c != '_')
  113. return false;
  114. return true;
  115. }
  116. /// \brief Create a new metric.
  117. ///
  118. /// Every metric is uniquely identified by its name and a set of key-value
  119. /// pairs, also known as labels. Prometheus's query language allows filtering
  120. /// and aggregation based on metric name and these labels.
  121. ///
  122. /// This example selects all time series that have the `http_requests_total`
  123. /// metric name:
  124. ///
  125. /// http_requests_total
  126. ///
  127. /// It is possible to assign labels to the metric name. These labels are
  128. /// propagated to each dimensional data added with Add(). For example if a
  129. /// label `job= "prometheus"` is provided to this constructor, it is possible
  130. /// to filter this time series with Prometheus's query language by appending
  131. /// a set of labels to match in curly braces ({})
  132. ///
  133. /// http_requests_total{job= "prometheus"}
  134. ///
  135. /// For further information see: [Quering Basics]
  136. /// (https://prometheus.io/docs/prometheus/latest/querying/basics/)
  137. ///
  138. /// \param name Set the metric name.
  139. /// \param help Set an additional description.
  140. /// \param constant_labels Assign a set of key-value pairs (= labels) to the
  141. /// metric. All these labels are propagated to each time series within the
  142. /// metric.
  143. /// \throw std::runtime_exception on invalid metric or label names.
  144. Family (Metric::Type type_, const std::string& name_, const std::string& help_, const Labels& constant_labels_)
  145. : type(type_), name(name_), help(help_), constant_labels(constant_labels_) {
  146. if (!CheckMetricName(name_))
  147. throw std::invalid_argument("Invalid metric name");
  148. for (const Label& label_pair : constant_labels) {
  149. const std::string& label_name = label_pair.first;
  150. if (!CheckLabelName(label_name))
  151. throw std::invalid_argument("Invalid label name");
  152. }
  153. }
  154. /// \brief Remove the given dimensional data.
  155. ///
  156. /// \param metric Dimensional data to be removed. The function does nothing,
  157. /// if the given metric was not returned by Add().
  158. void Remove (Metric* metric) {
  159. std::lock_guard<std::mutex> lock{ mutex };
  160. if (labels_reverse_lookup.count(metric) == 0)
  161. return;
  162. const Hash hash = labels_reverse_lookup.at(metric);
  163. metrics.erase(hash);
  164. labels.erase(hash);
  165. labels_reverse_lookup.erase(metric);
  166. }
  167. /// \brief Returns true if the dimensional data with the given labels exist
  168. ///
  169. /// \param labels A set of key-value pairs (= labels) of the dimensional data.
  170. bool Has (const Labels& labels) const {
  171. const Hash hash = hash_labels (labels);
  172. std::lock_guard<std::mutex> lock{ mutex };
  173. return metrics.find(hash) != metrics.end();
  174. }
  175. /// \brief Returns the name for this family.
  176. ///
  177. /// \return The family name.
  178. const std::string& GetName() const {
  179. return name;
  180. }
  181. /// \brief Returns the constant labels for this family.
  182. ///
  183. /// \return All constant labels as key-value pairs.
  184. const Labels& GetConstantLabels() const {
  185. return constant_labels;
  186. }
  187. /// \brief Returns the current value of each dimensional data.
  188. ///
  189. /// Collect is called by the Registry when collecting metrics.
  190. ///
  191. /// \return Zero or more samples for each dimensional data.
  192. MetricFamilies Collect() const override {
  193. std::lock_guard<std::mutex> lock{ mutex };
  194. if (metrics.empty())
  195. return {};
  196. MetricFamily family = MetricFamily{};
  197. family.type = type;
  198. family.name = name;
  199. family.help = help;
  200. family.metric.reserve(metrics.size());
  201. for (const std::pair<const Hash, MetricPtr>& metric_pair : metrics) {
  202. ClientMetric collected = metric_pair.second->Collect();
  203. for (const Label& constant_label : constant_labels)
  204. collected.label.emplace_back(ClientMetric::Label(constant_label.first, constant_label.second));
  205. const Labels& metric_labels = labels.at(metric_pair.first);
  206. for (const Label& metric_label : metric_labels)
  207. collected.label.emplace_back(ClientMetric::Label(metric_label.first, metric_label.second));
  208. family.metric.push_back(std::move(collected));
  209. }
  210. return { family };
  211. }
  212. };
  213. template <typename CustomMetric>
  214. class CustomFamily : public Family {
  215. public:
  216. static const Metric::Type static_type = CustomMetric::static_type;
  217. CustomFamily(const std::string& name, const std::string& help, const Family::Labels& constant_labels)
  218. : Family(static_type, name, help, constant_labels) {}
  219. /// \brief Add a new dimensional data.
  220. ///
  221. /// Each new set of labels adds a new dimensional data and is exposed in
  222. /// Prometheus as a time series. It is possible to filter the time series
  223. /// with Prometheus's query language by appending a set of labels to match in
  224. /// curly braces ({})
  225. ///
  226. /// http_requests_total{job= "prometheus",method= "POST"}
  227. ///
  228. /// \param labels Assign a set of key-value pairs (= labels) to the
  229. /// dimensional data. The function does nothing, if the same set of labels
  230. /// already exists.
  231. /// \param args Arguments are passed to the constructor of metric type T. See
  232. /// Counter, Gauge, Histogram or Summary for required constructor arguments.
  233. /// \return Return the newly created dimensional data or - if a same set of
  234. /// labels already exists - the already existing dimensional data.
  235. /// \throw std::runtime_exception on invalid label names.
  236. template <typename... Args>
  237. CustomMetric& Add (const Labels& new_labels, Args&&... args) {
  238. const Hash hash = hash_labels (new_labels);
  239. std::lock_guard<std::mutex> lock{ mutex };
  240. // try to find existing one
  241. auto metrics_iter = metrics.find(hash);
  242. if (metrics_iter != metrics.end()) {
  243. #ifndef NDEBUG
  244. // check that we have stored labels for this existing metric
  245. auto labels_iter = labels.find(hash);
  246. assert(labels_iter != labels.end());
  247. const Labels& stored_labels = labels_iter->second;
  248. assert(new_labels == stored_labels);
  249. #endif
  250. return dynamic_cast<CustomMetric&>(*metrics_iter->second);
  251. }
  252. // check labels before create the new one
  253. for (const Label& label_pair : new_labels) {
  254. const std::string& label_name = label_pair.first;
  255. if (!CheckLabelName(label_name))
  256. throw std::invalid_argument("Invalid label name");
  257. if (constant_labels.count(label_name))
  258. throw std::invalid_argument("Label name already present in constant labels");
  259. }
  260. // create new one
  261. std::unique_ptr<CustomMetric> metric_ptr (new CustomMetric(std::forward<Args>(args)...));
  262. CustomMetric& metric = *metric_ptr;
  263. const auto stored_metric = metrics.insert(std::make_pair(hash, std::move(metric_ptr)));
  264. assert(stored_metric.second);
  265. labels.insert({ hash, new_labels });
  266. labels_reverse_lookup.insert({ stored_metric.first->second.get(), hash });
  267. return metric;
  268. }
  269. /// \brief Return a builder to configure and register a Counter metric.
  270. ///
  271. /// @copydetails family_base_t<>::family_base_t()
  272. ///
  273. /// Example usage:
  274. ///
  275. /// \code
  276. /// auto registry = std::make_shared<Registry>();
  277. /// auto& counter_family = prometheus::Counter_family::build("some_name", "Additional description.", {{"key", "value"}}, *registry);
  278. ///
  279. /// ...
  280. /// \endcode
  281. ///
  282. /// \return An object of unspecified type T, i.e., an implementation detail
  283. /// except that it has the following members:
  284. ///
  285. /// - Name(const std::string&) to set the metric name,
  286. /// - Help(const std::string&) to set an additional description.
  287. /// - Label(const std::map<std::string, std::string>&) to assign a set of
  288. /// key-value pairs (= labels) to the metric.
  289. ///
  290. /// To finish the configuration of the Counter metric, register it with
  291. /// Register(Registry&).
  292. template <typename Registry>
  293. static CustomFamily& Build(Registry& registry, const std::string& name, const std::string& help, const Family::Labels& labels = Family::Labels()) {
  294. return registry.template Add<CustomFamily>(name, help, labels);
  295. }
  296. };
  297. } // namespace prometheus