blob: 0a1e7ccb33189a327a2b66f262690c3ffff1fa62 [file] [log] [blame]
Keir Mierlef4dfd872020-08-12 20:53:26 -07001// 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 Heplerf298de42021-03-19 15:06:36 -070020#include "pw_assert/check.h"
Keir Mierlef4dfd872020-08-12 20:53:26 -070021#include "pw_containers/vector.h"
22#include "pw_metric/metric.h"
23#include "pw_preprocessor/util.h"
24
25namespace pw::metric {
26namespace {
27
28class MetricWriter {
29 public:
Wyatt Heplerfa6edcc2021-08-20 08:30:08 -070030 MetricWriter(
31 MetricService::ServerWriter<pw_metric_MetricResponse>& response_writer)
Keir Mierlef4dfd872020-08-12 20:53:26 -070032 : 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 Heplerbad6d272022-02-16 07:15:07 -080074 response_writer_.Write(response_)
75 .IgnoreError(); // TODO(pwbug/387): Handle Status properly
Keir Mierlef4dfd872020-08-12 20:53:26 -070076 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 Heplerfa6edcc2021-08-20 08:30:08 -070083 MetricService::ServerWriter<pw_metric_MetricResponse>& response_writer_;
Keir Mierlef4dfd872020-08-12 20:53:26 -070084};
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.
90class MetricWalker {
91 public:
92 MetricWalker(MetricWriter& writer) : writer_(writer) {}
93
94 void Walk(const IntrusiveList<Metric>& metrics) {
95 for (const auto& m : metrics) {
Keir Mierled9e38fc2020-11-24 12:34:41 -080096 ScopedName scoped_name(m.name(), *this);
Keir Mierlef4dfd872020-08-12 20:53:26 -070097 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 Mierled9e38fc2020-11-24 12:34:41 -0800108 ScopedName scoped_name(group.name(), *this);
Keir Mierlef4dfd872020-08-12 20:53:26 -0700109 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 Bekkumfa1fc662020-10-16 16:11:13 -0700116 ScopedName(Token name, MetricWalker& rhs) : walker(rhs) {
Keir Mierlef4dfd872020-08-12 20:53:26 -0700117 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 Hepler8e756e32021-11-18 09:59:27 -0800132void MetricService::Get(const pw_metric_MetricRequest& /* request */,
Keir Mierlef4dfd872020-08-12 20:53:26 -0700133 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