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