# Copyright 2021 The Pigweed Authors # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. """Library to assist processing Snapshot Metadata protos into text""" from typing import Optional, List, Mapping import pw_log_tokenized import pw_tokenizer from pw_tokenizer import proto as proto_detokenizer from pw_snapshot_metadata_proto import snapshot_metadata_pb2 _PRETTY_FORMAT_DEFAULT_WIDTH = 80 def _process_tags(tags: Mapping[str, str]) -> Optional[str]: """Outputs snapshot tags as a multi-line string.""" if not tags: return None output: List[str] = ['Tags:'] for key, value in tags.items(): output.append(f' {key}: {value}') return '\n'.join(output) def process_snapshot(serialized_snapshot: bytes, tokenizer_db: Optional[pw_tokenizer.Detokenizer]) -> str: """Processes snapshot metadata and tags, producing a multi-line string.""" snapshot = snapshot_metadata_pb2.SnapshotBasicInfo() snapshot.ParseFromString(serialized_snapshot) output: List[str] = [] if snapshot.HasField('metadata'): output.extend(( str(MetadataProcessor(snapshot.metadata, tokenizer_db)), '', )) if snapshot.tags: tags = _process_tags(snapshot.tags) if tags: output.append(tags) # Trailing blank line for spacing. output.append('') return '\n'.join(output) class MetadataProcessor: """This class simplifies dumping contents of a snapshot Metadata message.""" def __init__(self, metadata: snapshot_metadata_pb2.Metadata, tokenizer_db: Optional[pw_tokenizer.Detokenizer]): self._metadata = metadata self._tokenizer_db = (tokenizer_db if tokenizer_db is not None else pw_tokenizer.Detokenizer(None)) self._format_width = _PRETTY_FORMAT_DEFAULT_WIDTH proto_detokenizer.detokenize_fields(self._tokenizer_db, self._metadata) def is_fatal(self) -> bool: return self._metadata.fatal def reason(self) -> str: if not self._metadata.reason: return 'UNKNOWN (field missing)' log = pw_log_tokenized.FormatStringWithMetadata( self._metadata.reason.decode()) return f'{log.file}: {log.message}' if log.file else log.message def project_name(self) -> str: return self._metadata.project_name.decode() def device_name(self) -> str: return self._metadata.device_name.decode() def device_fw_version(self) -> str: return self._metadata.software_version def snapshot_uuid(self) -> str: return self._metadata.snapshot_uuid.hex() def fw_build_uuid(self) -> str: return self._metadata.software_build_uuid.hex() def set_pretty_format_width(self, width: int): """Sets the centered width of the FATAL text for a formatted output.""" self._format_width = width def __str__(self) -> str: """outputs a pw.snapshot.Metadata proto as a multi-line string.""" output: List[str] = [] if self._metadata.fatal: output.extend(( '▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·'.center(self._format_width).rstrip(), '█▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █ '.center(self._format_width).rstrip(), '█ ▪ ▄█▀▀█ █. ▄█▀▀█ █ '.center(self._format_width).rstrip(), '▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌ '.center(self._format_width).rstrip(), '▀ ▀ ▀ · ▀ ▀ ▀ .▀▀'.center(self._format_width).rstrip(), '', 'Device crash cause:', )) else: output.append('Snapshot capture reason:') output.extend(( ' ' + self.reason(), '', )) if self._metadata.project_name: output.append(f'Project name: {self.project_name()}') if self._metadata.device_name: output.append(f'Device: {self.device_name()}') if self._metadata.software_version: output.append(f'Device FW version: {self.device_fw_version()}') if self._metadata.software_build_uuid: output.append(f'FW build UUID: {self.fw_build_uuid()}') if self._metadata.snapshot_uuid: output.append(f'Snapshot UUID: {self.snapshot_uuid()}') return '\n'.join(output)