Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 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_service_nanopb.h" |
| 16 | |
| 17 | #include <cstring> |
| 18 | #include <span> |
| 19 | |
Wyatt Hepler | f298de4 | 2021-03-19 15:06:36 -0700 | [diff] [blame] | 20 | #include "pw_assert/check.h" |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 21 | #include "pw_containers/vector.h" |
| 22 | #include "pw_metric/metric.h" |
| 23 | #include "pw_preprocessor/util.h" |
| 24 | |
| 25 | namespace pw::metric { |
| 26 | namespace { |
| 27 | |
| 28 | class MetricWriter { |
| 29 | public: |
Wyatt Hepler | fa6edcc | 2021-08-20 08:30:08 -0700 | [diff] [blame] | 30 | MetricWriter( |
| 31 | MetricService::ServerWriter<pw_metric_MetricResponse>& response_writer) |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 32 | : response_(pw_metric_MetricResponse_init_zero), |
| 33 | response_writer_(response_writer) {} |
| 34 | |
| 35 | // TODO(keir): Figure out a pw_rpc mechanism to fill a streaming packet based |
| 36 | // on transport MTU, rather than having this as a static knob. For example, |
| 37 | // some transports may be able to fit 30 metrics; others, only 5. |
| 38 | void Write(const Metric& metric, const Vector<Token>& path) { |
| 39 | // Nanopb doesn't offer an easy way to do bounds checking, so use span's |
| 40 | // type deduction magic to figure out the max size. |
| 41 | std::span<pw_metric_Metric> metrics(response_.metrics); |
| 42 | PW_CHECK_INT_LT(response_.metrics_count, metrics.size()); |
| 43 | |
| 44 | // Grab the next available Metric slot to write to in the response. |
| 45 | pw_metric_Metric& proto_metric = response_.metrics[response_.metrics_count]; |
| 46 | |
| 47 | // Copy the path. |
| 48 | std::span<Token> proto_path(proto_metric.token_path); |
| 49 | PW_CHECK_INT_LE(path.size(), proto_path.size()); |
| 50 | std::copy(path.begin(), path.end(), proto_path.begin()); |
| 51 | proto_metric.token_path_count = path.size(); |
| 52 | |
| 53 | // Copy the metric value. |
| 54 | if (metric.is_float()) { |
| 55 | proto_metric.value.as_float = metric.as_float(); |
| 56 | proto_metric.which_value = pw_metric_Metric_as_float_tag; |
| 57 | } else { |
| 58 | proto_metric.value.as_int = metric.as_int(); |
| 59 | proto_metric.which_value = pw_metric_Metric_as_int_tag; |
| 60 | } |
| 61 | |
| 62 | // Move write head to the next slot. |
| 63 | response_.metrics_count++; |
| 64 | |
| 65 | // If the metric response object is full, send the response and reset. |
| 66 | // TODO(keir): Support runtime batch sizes < max proto size. |
| 67 | if (response_.metrics_count == metrics.size()) { |
| 68 | Flush(); |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | void Flush() { |
| 73 | if (response_.metrics_count) { |
Wyatt Hepler | bad6d27 | 2022-02-16 07:15:07 -0800 | [diff] [blame] | 74 | response_writer_.Write(response_) |
| 75 | .IgnoreError(); // TODO(pwbug/387): Handle Status properly |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 76 | response_ = pw_metric_MetricResponse_init_zero; |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | private: |
| 81 | pw_metric_MetricResponse response_; |
| 82 | // This RPC stream writer handle must be valid for the metric writer lifetime. |
Wyatt Hepler | fa6edcc | 2021-08-20 08:30:08 -0700 | [diff] [blame] | 83 | MetricService::ServerWriter<pw_metric_MetricResponse>& response_writer_; |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 84 | }; |
| 85 | |
| 86 | // Walk a metric tree recursively; passing metrics with their path (names) to a |
| 87 | // metric writer which can consume them. |
| 88 | // |
| 89 | // TODO(keir): Generalize this to support a generic visitor. |
| 90 | class MetricWalker { |
| 91 | public: |
| 92 | MetricWalker(MetricWriter& writer) : writer_(writer) {} |
| 93 | |
| 94 | void Walk(const IntrusiveList<Metric>& metrics) { |
| 95 | for (const auto& m : metrics) { |
Keir Mierle | d9e38fc | 2020-11-24 12:34:41 -0800 | [diff] [blame] | 96 | ScopedName scoped_name(m.name(), *this); |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 97 | writer_.Write(m, path_); |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | void Walk(const IntrusiveList<Group>& groups) { |
| 102 | for (const auto& g : groups) { |
| 103 | Walk(g); |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | void Walk(const Group& group) { |
Keir Mierle | d9e38fc | 2020-11-24 12:34:41 -0800 | [diff] [blame] | 108 | ScopedName scoped_name(group.name(), *this); |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 109 | Walk(group.children()); |
| 110 | Walk(group.metrics()); |
| 111 | } |
| 112 | |
| 113 | private: |
| 114 | // Exists to safely push/pop parent groups from the explicit stack. |
| 115 | struct ScopedName { |
Ewout van Bekkum | fa1fc66 | 2020-10-16 16:11:13 -0700 | [diff] [blame] | 116 | ScopedName(Token name, MetricWalker& rhs) : walker(rhs) { |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 117 | PW_CHECK_INT_LT(walker.path_.size(), |
| 118 | walker.path_.capacity(), |
| 119 | "Metrics are too deep; bump path_ capacity"); |
| 120 | walker.path_.push_back(name); |
| 121 | } |
| 122 | ~ScopedName() { walker.path_.pop_back(); } |
| 123 | MetricWalker& walker; |
| 124 | }; |
| 125 | |
| 126 | Vector<Token, 4 /* max depth */> path_; |
| 127 | MetricWriter& writer_; |
| 128 | }; |
| 129 | |
| 130 | } // namespace |
| 131 | |
Wyatt Hepler | 8e756e3 | 2021-11-18 09:59:27 -0800 | [diff] [blame] | 132 | void MetricService::Get(const pw_metric_MetricRequest& /* request */, |
Keir Mierle | f4dfd87 | 2020-08-12 20:53:26 -0700 | [diff] [blame] | 133 | ServerWriter<pw_metric_MetricResponse>& response) { |
| 134 | // For now, ignore the request and just stream all the metrics back. |
| 135 | MetricWriter writer(response); |
| 136 | MetricWalker walker(writer); |
| 137 | |
| 138 | // This will stream all the metrics in the span of this Get() method call. |
| 139 | // This will have the effect of blocking the RPC thread until all the metrics |
| 140 | // are sent. That is likely to cause problems if there are many metrics, or |
| 141 | // if other RPCs are higher priority and should complete first. |
| 142 | // |
| 143 | // In the future, this should be replaced with an optional async solution |
| 144 | // that puts the application in control of when the response batches are sent. |
| 145 | walker.Walk(metrics_); |
| 146 | walker.Walk(groups_); |
| 147 | writer.Flush(); |
| 148 | } |
| 149 | |
| 150 | } // namespace pw::metric |