1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef SRC_TRACED_PROBES_FTRACE_COMPACT_SCHED_H_
18 #define SRC_TRACED_PROBES_FTRACE_COMPACT_SCHED_H_
19 
20 #include <stdint.h>
21 
22 #include "perfetto/ext/base/string_view.h"
23 #include "perfetto/protozero/packed_repeated_fields.h"
24 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
25 #include "src/traced/probes/ftrace/event_info_constants.h"
26 #include "src/traced/probes/ftrace/ftrace_config_utils.h"
27 
28 namespace perfetto {
29 
30 // The subset of the sched_switch event's format that is used when parsing and
31 // encoding into the compact format.
32 struct CompactSchedSwitchFormat {
33   uint32_t event_id;
34   uint16_t size;
35 
36   uint16_t next_pid_offset;
37   FtraceFieldType next_pid_type;
38   uint16_t next_prio_offset;
39   FtraceFieldType next_prio_type;
40   uint16_t prev_state_offset;
41   FtraceFieldType prev_state_type;
42   uint16_t next_comm_offset;
43 };
44 
45 // The subset of the sched_waking event's format that is used when parsing and
46 // encoding into the compact format.
47 struct CompactSchedWakingFormat {
48   uint32_t event_id;
49   uint16_t size;
50 
51   uint16_t pid_offset;
52   FtraceFieldType pid_type;
53   uint16_t target_cpu_offset;
54   FtraceFieldType target_cpu_type;
55   uint16_t prio_offset;
56   FtraceFieldType prio_type;
57   uint16_t comm_offset;
58 };
59 
60 // Pre-parsed format of a subset of scheduling events, for use during ftrace
61 // parsing if compact encoding is enabled. Holds a flag, |format_valid| to
62 // state whether the compile-time assumptions about the format held at runtime.
63 // If they didn't, we cannot use the compact encoding.
64 struct CompactSchedEventFormat {
65   // If false, the rest of the struct is considered invalid.
66   const bool format_valid;
67 
68   const CompactSchedSwitchFormat sched_switch;
69   const CompactSchedWakingFormat sched_waking;
70 };
71 
72 CompactSchedEventFormat ValidateFormatForCompactSched(
73     const std::vector<Event>& events);
74 
75 CompactSchedEventFormat InvalidCompactSchedEventFormatForTesting();
76 
77 // Compact encoding configuration used at ftrace reading & parsing time.
78 struct CompactSchedConfig {
CompactSchedConfigCompactSchedConfig79   CompactSchedConfig(bool _enabled) : enabled(_enabled) {}
80 
81   // If true, and sched_switch and/or sched_waking events are enabled, encode
82   // them in a compact format instead of the normal form.
83   const bool enabled = false;
84 };
85 
86 CompactSchedConfig CreateCompactSchedConfig(
87     const FtraceConfig& request,
88     const CompactSchedEventFormat& compact_format);
89 
90 CompactSchedConfig EnabledCompactSchedConfigForTesting();
91 CompactSchedConfig DisabledCompactSchedConfigForTesting();
92 
93 // Collects fields of sched_switch events, allowing them to be written out
94 // in a compact encoding.
95 class CompactSchedSwitchBuffer {
96  public:
timestamp()97   protozero::PackedVarInt& timestamp() { return timestamp_; }
prev_state()98   protozero::PackedVarInt& prev_state() { return prev_state_; }
next_pid()99   protozero::PackedVarInt& next_pid() { return next_pid_; }
next_prio()100   protozero::PackedVarInt& next_prio() { return next_prio_; }
next_comm_index()101   protozero::PackedVarInt& next_comm_index() { return next_comm_index_; }
102 
size()103   size_t size() const {
104     // Caller should fill all per-field buffers at the same rate.
105     return timestamp_.size();
106   }
107 
AppendTimestamp(uint64_t timestamp)108   inline void AppendTimestamp(uint64_t timestamp) {
109     timestamp_.Append(timestamp - last_timestamp_);
110     last_timestamp_ = timestamp;
111   }
112 
113   void Write(
114       protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const;
115   void Reset();
116 
117  private:
118   // First timestamp in a bundle is absolute. The rest are all delta-encoded,
119   // each relative to the preceding sched_switch timestamp.
120   uint64_t last_timestamp_ = 0;
121 
122   protozero::PackedVarInt timestamp_;
123   protozero::PackedVarInt prev_state_;
124   protozero::PackedVarInt next_pid_;
125   protozero::PackedVarInt next_prio_;
126   // Interning indices of the next_comm values. See |CommInterner|.
127   protozero::PackedVarInt next_comm_index_;
128 };
129 
130 // As |CompactSchedSwitchBuffer|, but for sched_waking events.
131 class CompactSchedWakingBuffer {
132  public:
pid()133   protozero::PackedVarInt& pid() { return pid_; }
target_cpu()134   protozero::PackedVarInt& target_cpu() { return target_cpu_; }
prio()135   protozero::PackedVarInt& prio() { return prio_; }
comm_index()136   protozero::PackedVarInt& comm_index() { return comm_index_; }
137 
size()138   size_t size() const {
139     // Caller should fill all per-field buffers at the same rate.
140     return timestamp_.size();
141   }
142 
AppendTimestamp(uint64_t timestamp)143   inline void AppendTimestamp(uint64_t timestamp) {
144     timestamp_.Append(timestamp - last_timestamp_);
145     last_timestamp_ = timestamp;
146   }
147 
148   void Write(
149       protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const;
150   void Reset();
151 
152  private:
153   uint64_t last_timestamp_ = 0;
154 
155   protozero::PackedVarInt timestamp_;
156   protozero::PackedVarInt pid_;
157   protozero::PackedVarInt target_cpu_;
158   protozero::PackedVarInt prio_;
159   // Interning indices of the comm values. See |CommInterner|.
160   protozero::PackedVarInt comm_index_;
161 };
162 
163 class CommInterner {
164  public:
165   static constexpr size_t kExpectedCommLength = 16;
166 
InternComm(const char * ptr)167   size_t InternComm(const char* ptr) {
168     // Linearly scan existing string views, ftrace reader will
169     // make sure this set doesn't grow too large.
170     base::StringView transient_view(ptr);
171     for (size_t i = 0; i < interned_comms_size_; i++) {
172       if (transient_view == interned_comms_[i]) {
173         return i;
174       }
175     }
176 
177     // Unique comm, intern it. Null byte is not copied over.
178     char* start = intern_buf_ + intern_buf_write_pos_;
179     size_t size = transient_view.size();
180     memcpy(start, ptr, size);
181     intern_buf_write_pos_ += size;
182 
183     size_t idx = interned_comms_size_;
184     base::StringView safe_view(start, size);
185     interned_comms_[interned_comms_size_++] = safe_view;
186 
187     PERFETTO_DCHECK(intern_buf_write_pos_ <= sizeof(intern_buf_));
188     PERFETTO_DCHECK(interned_comms_size_ < kMaxElements);
189     return idx;
190   }
191 
interned_comms_size()192   size_t interned_comms_size() const { return interned_comms_size_; }
193 
194   void Write(
195       protos::pbzero::FtraceEventBundle::CompactSched* compact_out) const;
196   void Reset();
197 
198  private:
199   // TODO(rsavitski): Consider making the storage dynamically-expandable instead
200   // to not rely on sizing the buffer for the worst case.
201   static constexpr size_t kMaxElements = 4096;
202 
203   char intern_buf_[kMaxElements * (kExpectedCommLength - 1)];
204   size_t intern_buf_write_pos_ = 0;
205 
206   // Views into unique interned comm strings. Even if every event carries a
207   // unique comm, the ftrace reader is expected to flush the compact buffer way
208   // before this reaches capacity. This is since the cost of processing each
209   // event grows with every unique interned comm (as the interning needs to
210   // search all existing internings).
211   std::array<base::StringView, kMaxElements> interned_comms_;
212   uint32_t interned_comms_size_ = 0;
213 };
214 
215 // Mutable state for buffering parts of scheduling events, that can later be
216 // written out in a compact format with |WriteAndReset|. Used by the ftrace
217 // reader.
218 class CompactSchedBuffer {
219  public:
sched_switch()220   CompactSchedSwitchBuffer& sched_switch() { return switch_; }
sched_waking()221   CompactSchedWakingBuffer& sched_waking() { return waking_; }
interner()222   CommInterner& interner() { return interner_; }
223 
224   // Writes out the currently buffered events, and starts the next batch
225   // internally.
226   void WriteAndReset(protos::pbzero::FtraceEventBundle* bundle);
227 
228  private:
229   CommInterner interner_;
230   CompactSchedSwitchBuffer switch_;
231   CompactSchedWakingBuffer waking_;
232 };
233 
234 }  // namespace perfetto
235 
236 #endif  // SRC_TRACED_PROBES_FTRACE_COMPACT_SCHED_H_
237