blob: ea3938096da9054834a757ec404b7b19bb46e112 [file] [log] [blame]
# 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
# 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
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()
output: List[str] = []
if snapshot.HasField('metadata'):
str(MetadataProcessor(snapshot.metadata, tokenizer_db)),
if snapshot.tags:
tags = _process_tags(snapshot.tags)
if tags:
# Trailing blank line for spacing.
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
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(
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:
'▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·'.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:',
output.append('Snapshot capture reason:')
' ' + 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)