1.. _module-pw_snapshot-module_usage: 2 3============ 4Module Usage 5============ 6Right now, pw_snapshot just dictates a *format*. That means there is no provided 7system information collection integration, underlying storage, or transport 8mechanism to fetch a snapshot from a device. These must be set up independently 9by your project. 10 11------------------- 12Building a Snapshot 13------------------- 14Even though a Snapshot is just a proto message, the potential size of the proto 15makes it important to consider the encoder. 16 17Nanopb is a popular encoder for embedded devices, it's impractical to use 18with the pw_snapshot proto. Nanopb works by generating in-memory structs that 19represent the protobuf message. Repeated, optional, and variable-length fields 20increase the size of the in-memory struct. The struct representation 21of snapshot-like protos can quickly near 10KB in size. Allocating 10KB 22 23Pigweed's pw_protobuf is a better choice as its design is centered around 24incrementally writing a proto directly to the final wire format. If you only 25write a few fields in a snapshot, you can do so with minimal memory overhead. 26 27.. code-block:: cpp 28 29 #include "pw_bytes/span.h" 30 #include "pw_protobuf/encoder.h" 31 #include "pw_snapshot_protos/snapshot.pwpb.h" 32 #include "pw_status/status.h" 33 #include "pw_stream/stream.h" 34 35 pw::Status EncodeSnapshot(pw::stream::Writer& writer, 36 pw::ByteSpan submessage_encode_buffer, 37 const CrashInfo &crash_info) { 38 // Create a snapshot proto encoder. 39 pw::snapshot::Snapshot::StreamEncoder snapshot_encoder( 40 writer, submessage_encode_buffer); 41 { // This scope is required to handle RAII behavior of the submessage. 42 // Start writing the Metadata submessage. 43 pw::snapshot::Metadata::StreamEncoder metadata_encoder = 44 snapshot_encoder.GetMetadataEncoder(); 45 metadata_encoder.WriteReason(EncodeReasonLog(crash_info)); 46 metadata_encoder.WriteFatal(true); 47 metadata_encoder.WriteProjectName(pw::as_bytes(pw::span("smart-shoe"))); 48 metadata_encoder.WriteDeviceName( 49 pw::as_bytes(pw::span("smart-shoe-p1"))); 50 } 51 return proto_encoder.status(); 52 } 53 54------------------- 55Custom Project Data 56------------------- 57There are two main ways to add custom project-specific data to a snapshot. Tags 58are the simplest way to capture small snippets of information that require 59no or minimal post-processing. For more complex data, it's usually more 60practical to extend the Snapshot proto. 61 62Tags 63==== 64Adding a key/value pair to the tags map is straightforward when using 65pw_protobuf. 66 67.. code-block:: cpp 68 69 { 70 pw::Snapshot::TagsEntry::StreamEncoder tags_encoder = 71 snapshot_encoder.GetTagsEncoder(); 72 tags_encoder.WriteKey("BtState"); 73 tags_encoder.WriteValue("connected"); 74 } 75 76Extending the Proto 77=================== 78Extending the Snapshot proto relies on proto behavior details that are explained 79in the :ref:`Snapshot Proto Format<module-pw_snapshot-proto_format>`. Extending 80the snapshot proto is as simple as defining a proto message that **only** 81declares fields with numbers that are reserved by the Snapshot proto for 82downstream projects. When encoding your snapshot, you can then write both the 83upstream Snapshot proto and your project's custom extension proto message to the 84same proto encoder. 85 86The upstream snapshot tooling will ignore any project-specific proto data, 87the proto data can be decoded a second time using a project-specific proto. At 88that point, any handling logic of the project-specific data would have to be 89done as part of project-specific tooling. 90 91------------------- 92Analyzing Snapshots 93------------------- 94Snapshots can be processed for analysis using the ``pw_snapshot.process`` python 95tool. This tool turns a binary snapshot proto into human readable, actionable 96information. As some snapshot fields may optionally be tokenized, a 97pw_tokenizer database or ELF file with embedded pw_tokenizer tokens may 98optionally be passed to the tool to detokenize applicable fields. 99 100.. code-block:: sh 101 102 # Example invocation, which dumps to stdout by default. 103 $ python -m pw_snapshot.processor path/to/serialized_snapshot.bin 104 105 106 ____ _ __ _____ _ _____ ____ _____ __ ______ ______ 107 / __ \ | / / / ___// | / / | / __ \/ ___// / / / __ \/_ __/ 108 / /_/ / | /| / / \__ \/ |/ / /| | / /_/ /\__ \/ /_/ / / / / / / 109 / ____/| |/ |/ / ___/ / /| / ___ |/ ____/___/ / __ / /_/ / / / 110 /_/ |__/|__/____/____/_/ |_/_/ |_/_/ /____/_/ /_/\____/ /_/ 111 /_____/ 112 113 114 ▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ · 115 █▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █ 116 █ ▪ ▄█▀▀█ █. ▄█▀▀█ █ 117 ▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌ 118 ▀ ▀ ▀ · ▀ ▀ ▀ .▀▀ 119 120 Device crash cause: 121 ../examples/example_rpc.cc: Assert failed: 1+1 == 42 122 123 Project name: gShoe 124 Device: GSHOE-QUANTUM_CORE-REV_0.1 125 Device FW version: QUANTUM_CORE-0.1.325-e4a84b1a 126 FW build UUID: ad2d39258c1bc487f07ca7e04991a836fdf7d0a0 127 Snapshot UUID: 8481bb12a162164f5c74855f6d94ea1a 128 129 Thread State 130 2 threads running, Main Stack (Handler Mode) active at the time of capture. 131 ~~~~~~~~~~~~~~~~~~~~~~~~~ 132 133 Thread (INTERRUPT_HANDLER): Main Stack (Handler Mode) <-- [ACTIVE] 134 Est CPU usage: unknown 135 Stack info 136 Stack used: 0x2001b000 - 0x2001ae20 (480 bytes) 137 Stack limits: 0x2001b000 - 0x???????? (size unknown) 138 Raw Stack 139 00caadde 140 141 142 Thread (RUNNING): Idle 143 Est CPU usage: unknown 144 Stack info 145 Stack used: 0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%) 146 Stack limits: 0x2001ac00 - 0x2001aa00 (512 bytes) 147 148--------------------- 149Symbolizing Addresses 150--------------------- 151The snapshot processor tool has built-in support for symbolization of some data 152embedded into snapshots. Taking advantage of this requires the use of a 153project-provided ``SymbolizerMatcher`` callback. This is used by the snapshot 154processor to understand which ELF file should be used to symbolize which 155snapshot in cases where a snapshot has related snapshots embedded inside of it. 156 157Here's an example implementation that uses the device name: 158 159.. code-block:: py 160 161 # Given a firmware bundle directory, determine the ELF file associated with 162 # the provided snapshot. 163 def _snapshot_symbolizer_matcher(fw_bundle_dir: Path, 164 snapshot: snapshot_pb2.Snapshot 165 ) -> Symbolizer: 166 metadata = MetadataProcessor(snapshot.metadata, DETOKENIZER) 167 if metadata.device_name().startswith('GSHOE_MAIN_CORE'): 168 return LlvmSymbolizer(fw_bundle_dir / 'main.elf') 169 if metadata.device_name().startswith('GSHOE_SENSOR_CORE'): 170 return LlvmSymbolizer(fw_bundle_dir / 'sensors.elf') 171 return LlvmSymbolizer() 172 173 174 # A project specific wrapper to decode snapshots that provides a detokenizer 175 # and ElfMatcher. 176 def decode_snapshots(snapshot: bytes, fw_bundle_dir: Path) -> str: 177 178 # This is the actual ElfMatcher, which wraps the helper in a lambda that 179 # captures the passed firmware artifacts directory. 180 matcher: processor.SymbolizerMatcher = ( 181 lambda snapshot: _snapshot_symbolizer_matcher( 182 fw_bundle_dir, snapshot)) 183 return processor.process_snapshots(snapshot, DETOKENIZER, matcher) 184 185------------- 186C++ Utilities 187------------- 188 189UUID utilities 190============== 191Snapshot UUIDs are used to uniquely identify snapshots. Pigweed strongly 192recommends using randomly generated data as a snapshot UUID. The 193more entropy and random bits, the lower the probability that two devices will 194produce the same UUID for a snapshot. 16 bytes should be sufficient for most 195projects, so this module provides ``UuidSpan`` and ``ConstUuidSpan`` types that 196can be helpful for referring to UUID-sized byte spans. 197 198Reading a snapshot's UUID 199------------------------- 200An in-memory snapshot's UUID may be read using ``ReadUuidFromSnapshot()``. 201 202.. code-block:: cpp 203 204 void NotifyNewSnapshot(ConstByteSpan snapshot) { 205 std::array<std::byte, pw::snapshot::kUuidSizeBytes> uuid; 206 pw::Result<pw::ConstByteSpan> result = 207 pw::snapshot::ReadUuidFromSnapshot(snapshot, uuid); 208 if (!result.ok()) { 209 PW_LOG_ERROR("Failed to read UUID from new snapshot, error code %d", 210 static_cast<int>(result.status().code())); 211 return; 212 } 213 LogNewSnapshotUuid(result.value()); 214 } 215