1 // Copyright 2022 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_pwpb.h"
16
17 #include <cstring>
18
19 #include "pw_assert/check.h"
20 #include "pw_containers/vector.h"
21 #include "pw_metric/metric.h"
22 #include "pw_metric_private/metric_walker.h"
23 #include "pw_metric_proto/metric_service.pwpb.h"
24 #include "pw_preprocessor/util.h"
25 #include "pw_rpc/raw/server_reader_writer.h"
26 #include "pw_span/span.h"
27 #include "pw_status/status.h"
28 #include "pw_status/try.h"
29
30 namespace pw::metric {
31
32 // TODO(amontanez): Make this follow the metric_service.options configuration.
33 constexpr size_t kMaxNumPackedEntries = 3;
34
35 namespace {
36
37 class PwpbMetricWriter : public virtual internal::MetricWriter {
38 public:
PwpbMetricWriter(span<std::byte> response,rpc::RawServerWriter & response_writer)39 PwpbMetricWriter(span<std::byte> response,
40 rpc::RawServerWriter& response_writer)
41 : response_(response),
42 response_writer_(response_writer),
43 encoder_(response) {}
44
45 // TODO(keir): Figure out a pw_rpc mechanism to fill a streaming packet based
46 // on transport MTU, rather than having this as a static knob. For example,
47 // some transports may be able to fit 30 metrics; others, only 5.
Write(const Metric & metric,const Vector<Token> & path)48 Status Write(const Metric& metric, const Vector<Token>& path) override {
49 { // Scope to control proto_encoder lifetime.
50
51 // Grab the next available Metric slot to write to in the response.
52 proto::pwpb::Metric::StreamEncoder proto_encoder =
53 encoder_.GetMetricsEncoder();
54 PW_TRY(proto_encoder.WriteTokenPath(path));
55 // Encode the metric value.
56 if (metric.is_float()) {
57 PW_TRY(proto_encoder.WriteAsFloat(metric.as_float()));
58 } else {
59 PW_TRY(proto_encoder.WriteAsInt(metric.as_int()));
60 }
61
62 metrics_count++;
63 }
64
65 if (metrics_count == kMaxNumPackedEntries) {
66 return Flush();
67 }
68 return OkStatus();
69 }
70
Flush()71 Status Flush() {
72 Status status;
73 if (metrics_count) {
74 status = response_writer_.Write(encoder_);
75 // Different way to clear MemoryEncoder. Copy constructor is disabled
76 // for memory encoder, and there is no "clear()" method.
77 encoder_.~MemoryEncoder();
78 new (&encoder_) proto::pwpb::MetricRequest::MemoryEncoder(response_);
79 metrics_count = 0;
80 }
81 return status;
82 }
83
84 private:
85 span<std::byte> response_;
86 // This RPC stream writer handle must be valid for the metric writer
87 // lifetime.
88 rpc::RawServerWriter& response_writer_;
89 proto::pwpb::MetricRequest::MemoryEncoder encoder_;
90 size_t metrics_count = 0;
91 };
92 } // namespace
93
Get(ConstByteSpan,rpc::RawServerWriter & raw_response)94 void MetricService::Get(ConstByteSpan /*request*/,
95 rpc::RawServerWriter& raw_response) {
96 // For now, ignore the request and just stream all the metrics back.
97 // TODO(amontanez): Make this follow the metric_service.options configuration.
98 constexpr size_t kSizeOfOneMetric =
99 pw::metric::proto::pwpb::MetricResponse::kMaxEncodedSizeBytes +
100 pw::metric::proto::pwpb::Metric::kMaxEncodedSizeBytes;
101 constexpr size_t kEncodeBufferSize = kMaxNumPackedEntries * kSizeOfOneMetric;
102
103 std::array<std::byte, kEncodeBufferSize> encode_buffer;
104
105 PwpbMetricWriter writer(encode_buffer, raw_response);
106 internal::MetricWalker walker(writer);
107
108 // This will stream all the metrics in the span of this Get() method call.
109 // This will have the effect of blocking the RPC thread until all the metrics
110 // are sent. That is likely to cause problems if there are many metrics, or
111 // if other RPCs are higher priority and should complete first.
112 //
113 // In the future, this should be replaced with an optional async solution
114 // that puts the application in control of when the response batches are sent.
115
116 // Propagate status through walker.
117 Status status;
118 status.Update(walker.Walk(metrics_));
119 status.Update(walker.Walk(groups_));
120 status.Update(writer.Flush());
121 raw_response.Finish(status).IgnoreError();
122 }
123 } // namespace pw::metric
124