• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_metric/metric.h"
16 
17 #include <array>
18 #include <atomic>
19 #include <limits>
20 
21 #include "pw_assert/check.h"
22 #include "pw_log/log.h"
23 #include "pw_preprocessor/compiler.h"
24 #include "pw_span/span.h"
25 #include "pw_tokenizer/base64.h"
26 
27 namespace pw::metric {
28 namespace {
29 
30 template <typename T>
AsSpan(const T & t)31 span<const std::byte> AsSpan(const T& t) {
32   return span<const std::byte>(reinterpret_cast<const std::byte*>(&t),
33                                sizeof(t));
34 }
35 
36 // A convenience class to encode a token as base64 while managing the storage.
37 // TODO(keir): Consider putting this into upstream pw_tokenizer.
38 struct Base64EncodedToken {
Base64EncodedTokenpw::metric::__anon0ffe66cc0111::Base64EncodedToken39   Base64EncodedToken(Token token) {
40     size_t encoded_size = tokenizer::PrefixedBase64Encode(AsSpan(token), data);
41     data[encoded_size] = 0;
42   }
43 
valuepw::metric::__anon0ffe66cc0111::Base64EncodedToken44   const char* value() { return data.data(); }
45   std::array<char, 16> data;
46 };
47 
Indent(int level)48 const char* Indent(int level) {
49   static const char* kWhitespace10 = "          ";
50   level = std::min(level, 4);
51   return kWhitespace10 + 8 - 2 * level;
52 }
53 
54 }  // namespace
55 
56 // Enable easier registration when used as a member.
Metric(Token name,float value,IntrusiveList<Metric> & metrics)57 Metric::Metric(Token name, float value, IntrusiveList<Metric>& metrics)
58     : Metric(name, value) {
59   metrics.push_front(*this);
60 }
Metric(Token name,uint32_t value,IntrusiveList<Metric> & metrics)61 Metric::Metric(Token name, uint32_t value, IntrusiveList<Metric>& metrics)
62     : Metric(name, value) {
63   metrics.push_front(*this);
64 }
65 
as_float() const66 float Metric::as_float() const {
67   PW_DCHECK(is_float());
68   return float_.load(std::memory_order_relaxed);
69 }
70 
as_int() const71 uint32_t Metric::as_int() const {
72   PW_DCHECK(is_int());
73   return uint_.load(std::memory_order_relaxed);
74 }
75 
Increment(uint32_t amount)76 void Metric::Increment(uint32_t amount) {
77   PW_DCHECK(is_int());
78 
79   uint32_t value = uint_.load();
80   uint32_t updated;
81 
82   if (value == std::numeric_limits<uint32_t>::max()) {
83     return;
84   }
85 
86   do {
87     if (PW_ADD_OVERFLOW(value, amount, &updated)) {
88       updated = std::numeric_limits<uint32_t>::max();
89     }
90   } while (!uint_.compare_exchange_weak(value, updated));
91 }
92 
Decrement(uint32_t amount)93 void Metric::Decrement(uint32_t amount) {
94   PW_DCHECK(is_int());
95 
96   uint32_t value = uint_.load();
97   uint32_t updated;
98 
99   do {
100     if (value == 0) {
101       return;
102     }
103 
104     if (PW_SUB_OVERFLOW(value, amount, &updated)) {
105       updated = 0;
106     }
107   } while (!uint_.compare_exchange_weak(value, updated));
108 }
109 
SetInt(uint32_t value)110 void Metric::SetInt(uint32_t value) {
111   PW_DCHECK(is_int());
112   uint_.store(value, std::memory_order_relaxed);
113 }
114 
SetFloat(float value)115 void Metric::SetFloat(float value) {
116   PW_DCHECK(is_float());
117   float_.store(value, std::memory_order_relaxed);
118 }
119 
Dump(int level,bool last) const120 void Metric::Dump(int level, bool last) const {
121   Base64EncodedToken encoded_name(name());
122   const char* indent = Indent(level);
123   const char* comma = last ? "" : ",";
124   if (is_float()) {
125     // Variadic macros promote float to double. Explicitly cast here to
126     // acknowledge this and allow projects to use -Wdouble-promotion.
127     PW_LOG_INFO("%s \"%s\": %f%s",
128                 indent,
129                 encoded_name.value(),
130                 static_cast<double>(as_float()),
131                 comma);
132   } else {
133     PW_LOG_INFO("%s \"%s\": %u%s",
134                 indent,
135                 encoded_name.value(),
136                 static_cast<unsigned int>(as_int()),
137                 comma);
138   }
139 }
140 
Dump(const IntrusiveList<Metric> & metrics,int level)141 void Metric::Dump(const IntrusiveList<Metric>& metrics, int level) {
142   auto iter = metrics.begin();
143   while (iter != metrics.end()) {
144     const Metric& m = *iter++;
145     m.Dump(level, iter == metrics.end());
146   }
147 }
148 
Group(Token name,IntrusiveList<Group> & groups)149 Group::Group(Token name, IntrusiveList<Group>& groups) : name_(name) {
150   groups.push_front(*this);
151 }
152 
Dump() const153 void Group::Dump() const {
154   PW_LOG_INFO("{");
155   Dump(0, true);
156   PW_LOG_INFO("}");
157 }
158 
Dump(int level,bool last) const159 void Group::Dump(int level, bool last) const {
160   Base64EncodedToken encoded_name(name());
161   const char* indent = Indent(level);
162   const char* comma = last ? "" : ",";
163   PW_LOG_INFO("%s\"%s\": {", indent, encoded_name.value());
164   Group::Dump(children(), level + 1);
165   Metric::Dump(metrics(), level + 1);
166   PW_LOG_INFO("%s}%s", indent, comma);
167 }
168 
Dump(const IntrusiveList<Group> & groups,int level)169 void Group::Dump(const IntrusiveList<Group>& groups, int level) {
170   auto iter = groups.begin();
171   while (iter != groups.end()) {
172     const Group& g = *iter++;
173     g.Dump(level, iter == groups.end());
174   }
175 }
176 
177 }  // namespace pw::metric
178