| // Copyright 2024 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "libpmt/pmt_decoder.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <ios> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/to_string.h> |
| #include <re2/re2.h> |
| |
| #include "bits/pmt_data_interface.h" |
| #include "bits/pmt_metadata.h" |
| #include "libpmt/bits/pmt_data.pb.h" |
| #include "libpmt/pmt_impl.h" |
| #include "libpmt/xml_helper.h" |
| |
| namespace pmt { |
| |
| namespace { |
| |
| // Shortcut to keep parsing error logs consistent. |
| #define LOG_ERROR_AND_RETURN(result, msg) \ |
| { \ |
| LOG(ERROR) << msg << "."; \ |
| return result; \ |
| } \ |
| while (0) |
| |
| #define PMT_XML_ERROR_EXIT(msg) \ |
| LOG_ERROR_AND_RETURN(result, \ |
| "Failed to parse PMT metadata mapping file: " << msg) |
| |
| #define AGG_XML_ERROR_EXIT(msg) \ |
| LOG_ERROR_AND_RETURN(-EBADF, "Failed to parse PMT metadata file: " << msg) |
| |
| // Node names used in metadata file parsing. |
| constexpr char kAttrGuid[] = "guid"; |
| constexpr char kAttrTransformId[] = "transformID"; |
| constexpr char kAttrDataTypeId[] = "datatypeID"; |
| |
| // XPaths used in metadata file parsing. |
| constexpr char kXPathMappings[] = "/pmt/mappings/mapping"; |
| constexpr char kXPathBaseDir[] = "./xmlset/basedir"; |
| constexpr char kXPathAggregatorFile[] = "./xmlset/aggregator"; |
| constexpr char kXPathAggregatorInterfaceFile[] = "./xmlset/aggregatorinterface"; |
| constexpr char kXPathCommonFile[] = "./xmlset/common"; |
| constexpr char kXPathTransforms[] = |
| "/TELI:AggregatorInterface/cmn:TransFormations/cmn:TransFormation"; |
| constexpr char kXPathTransformType[] = "./cmn:output_dataclass"; |
| constexpr char kXPathSamples[] = |
| "/TELEM:Aggregator/TELEM:SampleGroup/TELC:sample"; |
| constexpr char kXPathLsb[] = "./TELC:lsb"; |
| constexpr char kXPathMsb[] = "./TELC:msb"; |
| constexpr char kXPathDescription[] = "./TELC:description"; |
| constexpr char kXPathSubgroup[] = "./TELC:sampleSubGroup"; |
| constexpr char kXPathTransformRef[] = "./TELI:transformREF"; |
| constexpr char kXPathDataTypes[] = "/TELC:DataTypes/TELC:dataType"; |
| |
| // XML namespaces present within metadata files. |
| constexpr char kXsiNs[] = "xsi"; |
| constexpr char kXiNs[] = "xi"; |
| constexpr char kTELCNs[] = "TELC"; |
| constexpr char kTELEMNs[] = "TELEM"; |
| constexpr char kTELINs[] = "TELI"; |
| // Libxml2 does not support nodes with namespace but no prefix in XPath search. |
| // Therefore an artificial prefix is chosen. |
| constexpr char kCommonNs[] = "cmn"; |
| |
| constexpr char kXsiNsUri[] = "http://www.w3.org/2001/XMLSchema-instance"; |
| constexpr char kTELCNsUri[] = "http://schemas.intel.com/telemetry/base/common"; |
| constexpr char kTELEMNsUri[] = |
| "http://schemas.intel.com/telemetry/base/aggregator"; |
| constexpr char kXiNsUri[] = "http://www.w3.org/2001/XInclude"; |
| constexpr char kTELINsUri[] = |
| "http://schemas.intel.com/telemetry/interface/aggregatorinterface"; |
| constexpr char kCommonNsUri[] = |
| "http://schemas.intel.com/telemetry/base/common"; |
| |
| // Regular expression to detect fields to skip. |
| constexpr char kRsvdRegExp[] = "reserved|rsvd|spare"; |
| |
| // Sample names which require special handling. |
| constexpr char kSamplePkgcBlockRefcnt[] = "PACKAGE_CSTATE_BLOCK_REFCNT"; |
| constexpr char kSamplePkgcWakeRefcnt[] = "PACKAGE_CSTATE_WAKE_REFCNT"; |
| |
| // Extract an [msb, lsb] unsigned field from an 8B word. |
| constexpr uint64_t GetField(uint64_t v, unsigned int msb, unsigned int lsb) { |
| return (v >> lsb) & ((uint64_t{1} << (msb - lsb + 1)) - 1); |
| } |
| |
| } // namespace |
| |
| // Shorten calls to frequently used symbols. |
| using std::string, std::unordered_map, std::vector, std::hex; |
| |
| PmtDecoder::PmtDecoder() : intf_(new PmtSysfsData()) {} |
| |
| PmtDecoder::~PmtDecoder() { |
| CleanUpDecoding(); |
| } |
| |
| PmtDecoder::PmtDecoder(std::unique_ptr<PmtDataInterface> intf) |
| : intf_(std::move(intf)) {} |
| |
| unordered_map<Guid, struct PmtDecoder::MetadataFilePaths> |
| PmtDecoder::FindMetadata() { |
| unordered_map<Guid, struct MetadataFilePaths> result; |
| |
| base::FilePath meta_path; |
| meta_path = intf_->GetMetadataMappingsFile(); |
| if (meta_path.empty()) { |
| PMT_XML_ERROR_EXIT("pmt.xml is missing"); |
| } |
| |
| xml::XmlParser parser; |
| int parse_result = parser.ParseFile(meta_path); |
| if (parse_result != 0) { |
| PMT_XML_ERROR_EXIT("Failed to parse " << meta_path << ": " |
| << strerror(parse_result)); |
| } |
| |
| xml::ScopedXmlXPathObject mappings_match = parser.XPathEval(kXPathMappings); |
| if (!mappings_match || !mappings_match->nodesetval || |
| !mappings_match->nodesetval->nodeTab) { |
| PMT_XML_ERROR_EXIT("failed to find " << kXPathMappings); |
| } |
| |
| xmlNodeSetPtr mappings = mappings_match->nodesetval; |
| |
| // For each mapping, read its GUID and basedir. If basedir doesn't exist, skip |
| // it. Otherwise find the aggregator and aggregator interface files ensure |
| // they exist. |
| for (size_t i = 0; i < mappings->nodeNr; i++) { |
| MetadataFilePaths guid_paths; |
| xmlNodePtr mapping = mappings->nodeTab[i]; |
| |
| auto guid_str = parser.GetAttrValue(mapping, kAttrGuid); |
| Guid guid; |
| if (!guid_str || !base::HexStringToUInt(*guid_str, &guid)) { |
| PMT_XML_ERROR_EXIT("could not decode GUID " << *guid_str); |
| } |
| |
| auto base_dir = parser.GetXPathNodeTextValue(mapping, kXPathBaseDir); |
| if (!base_dir) { |
| PMT_XML_ERROR_EXIT("malformed <basedir>"); |
| } |
| |
| base::FilePath base_dir_path(meta_path.DirName().Append(base_dir->data())); |
| // If the path doesn't exist it simply means it's not supported so skip it. |
| if (!base::DirectoryExists(base_dir_path)) { |
| continue; |
| } |
| |
| auto agg_file = parser.GetXPathNodeTextValue(mapping, kXPathAggregatorFile); |
| if (!agg_file) { |
| PMT_XML_ERROR_EXIT("malformed <aggregator>"); |
| } |
| guid_paths.aggregator_ = base_dir_path.Append(*agg_file); |
| if (!base::PathExists(guid_paths.aggregator_)) { |
| PMT_XML_ERROR_EXIT(guid_paths.aggregator_.value() << " doesn't exist"); |
| } |
| |
| auto agg_intf = |
| parser.GetXPathNodeTextValue(mapping, kXPathAggregatorInterfaceFile); |
| if (!agg_intf) { |
| PMT_XML_ERROR_EXIT("malformed <aggregatorinterface>"); |
| } |
| guid_paths.aggregator_interface_ = base_dir_path.Append(*agg_intf); |
| if (!base::PathExists(guid_paths.aggregator_interface_)) { |
| PMT_XML_ERROR_EXIT(guid_paths.aggregator_interface_.value() |
| << " doesn't exist"); |
| } |
| |
| auto common_file = parser.GetXPathNodeTextValue(mapping, kXPathCommonFile); |
| if (!common_file) { |
| PMT_XML_ERROR_EXIT("malformed <common>"); |
| } |
| guid_paths.common_ = base_dir_path.Append(*common_file); |
| if (!base::PathExists(guid_paths.common_)) { |
| PMT_XML_ERROR_EXIT(guid_paths.common_.value() << " doesn't exist"); |
| } |
| |
| result[guid] = guid_paths; |
| } |
| |
| return result; |
| } |
| |
| vector<Guid> PmtDecoder::DetectMetadata() { |
| unordered_map<Guid, struct MetadataFilePaths> guid_map = FindMetadata(); |
| vector<Guid> result; |
| for (const auto& kv : guid_map) { |
| result.push_back(kv.first); |
| } |
| std::sort(result.begin(), result.end()); |
| return result; |
| } |
| |
| int PmtDecoder::SetUpDecoding(const vector<Guid> guids) { |
| // Sort by GUIDs. GUIDs need to be sorted because some transformations |
| // are relying on data from other devices (see the 'pkgc_block_cause' |
| // transformation). |
| if (!ctx_.info_.empty()) { |
| return -EBUSY; |
| } |
| |
| // Prepare RegExp for skipping reserved samples later. |
| re2::RE2::Options opts; |
| opts.set_case_sensitive(false); |
| re2::RE2 samples_to_skip(kRsvdRegExp, opts); |
| |
| auto sorted_guids = guids; |
| std::sort(sorted_guids.begin(), sorted_guids.end()); |
| auto supported_guids = FindMetadata(); |
| |
| // 1st pass through guids to check if metadata is available for all. |
| for (const auto& guid : guids) { |
| if (!supported_guids.contains(guid)) { |
| LOG(ERROR) << "GUID 0x" << hex << guid << " not supported"; |
| return -EINVAL; |
| } |
| } |
| |
| // Do a pass on aggregator interface files to gather all transformations. |
| unordered_map<string, DataType> transform_map; |
| for (const auto& guid : guids) { |
| auto& metadata_files = supported_guids[guid]; |
| |
| // Setup parser for aggregator interface file. That's where transformations |
| // are described. |
| xml::XmlParser agg_intf_parser; |
| int result = |
| agg_intf_parser.ParseFile(metadata_files.aggregator_interface_); |
| if (result) { |
| AGG_XML_ERROR_EXIT(metadata_files.aggregator_interface_ |
| << ": " << strerror(result)); |
| } |
| |
| agg_intf_parser.RegisterNamespace(kCommonNs, kCommonNsUri); |
| agg_intf_parser.RegisterNamespace(kXiNs, kXiNsUri); |
| agg_intf_parser.RegisterNamespace(kTELINs, kTELINsUri); |
| |
| xml::ScopedXmlXPathObject transforms_match = |
| agg_intf_parser.XPathEval(kXPathTransforms); |
| if (!transforms_match || !transforms_match->nodesetval || |
| !transforms_match->nodesetval->nodeTab) { |
| AGG_XML_ERROR_EXIT("failed to find " << kXPathTransforms); |
| } |
| |
| xmlNodeSetPtr transforms = transforms_match->nodesetval; |
| // For each transformation, read its output datatype as we'll need it later |
| // to select the transformation type. For safety check whether earlier |
| // entries (from other GUIDs) are consistent and error out if they're not. |
| for (size_t i = 0; i < transforms->nodeNr; i++) { |
| auto transform = transforms->nodeTab[i]; |
| auto id = agg_intf_parser.GetAttrValue(transform, kAttrTransformId); |
| if (!id) { |
| AGG_XML_ERROR_EXIT("failed to find " << kAttrTransformId |
| << " in a transformation node"); |
| } |
| auto output_dataclass = |
| agg_intf_parser.GetXPathNodeTextValue(transform, kXPathTransformType); |
| if (!output_dataclass) { |
| AGG_XML_ERROR_EXIT("failed to parse the type of " << *id); |
| } |
| // Determine the type. Most samples are floats. For others, default to an |
| // unsigned integer. |
| DataType type = DataType::FLOAT; |
| if (*output_dataclass != "float") { |
| // Try to detect if it's a signed integer. This is unfortunately not |
| // explicitly given but the "Sxxx" transformID seems to indicate a |
| // signed integer as the transformation is essentially a U2 |
| // representation of it. |
| if (id->starts_with("S")) { |
| type = DataType::SINT; |
| } else { |
| type = DataType::UINT; |
| } |
| } |
| auto transform_id = string(*id); |
| if (transform_map.contains(transform_id)) { |
| if (transform_map[transform_id] != type) { |
| AGG_XML_ERROR_EXIT("conflicting transformation types for " |
| << transform_id << ": " |
| << transform_map[transform_id] << " != " << type); |
| } |
| } else { |
| transform_map[transform_id] = type; |
| } |
| } |
| } |
| |
| // Do a pass on common files to gather sample unit information. |
| unordered_map<string, string> unit_map; |
| for (const auto& guid : guids) { |
| auto& metadata_files = supported_guids[guid]; |
| |
| // Setup parser for common file. That's where sample units |
| // are defined. |
| xml::XmlParser common_parser; |
| int result = common_parser.ParseFile(metadata_files.common_); |
| if (result) { |
| AGG_XML_ERROR_EXIT(metadata_files.common_ << ": " << strerror(result)); |
| } |
| |
| common_parser.RegisterNamespace(kXsiNs, kXsiNsUri); |
| common_parser.RegisterNamespace(kTELCNs, kTELCNsUri); |
| |
| xml::ScopedXmlXPathObject datatypes_match = |
| common_parser.XPathEval(kXPathDataTypes); |
| if (!datatypes_match || !datatypes_match->nodesetval || |
| !datatypes_match->nodesetval->nodeTab) { |
| AGG_XML_ERROR_EXIT("failed to find " << kXPathDataTypes); |
| } |
| |
| xmlNodeSetPtr datatypes = datatypes_match->nodesetval; |
| // For each datatype, read its datatypeID attribute |
| // as it serves as reference in aggregator files. |
| for (size_t i = 0; i < datatypes->nodeNr; i++) { |
| auto datatype = datatypes->nodeTab[i]; |
| auto id = common_parser.GetAttrValue(datatype, kAttrDataTypeId); |
| if (!id) { |
| AGG_XML_ERROR_EXIT("failed to find " << kAttrDataTypeId |
| << " in a dataype node"); |
| } |
| // Check if we already have a unit for this datatypeID. |
| if (unit_map.contains(*id)) { |
| continue; |
| } |
| |
| // Read unit name and symbol if unit is defined |
| // and save it in unit_map for later use. |
| auto units_match = common_parser.XPathNodeEval(datatype, "./TELC:units"); |
| if (!units_match || !units_match->nodesetval || |
| units_match->nodesetval->nodeNr < 1) { |
| unit_map[*id] = ""; |
| continue; |
| } |
| if (units_match->nodesetval->nodeNr > 1) { |
| LOG(WARNING) << "More than one unit defined for TELC:datatype " << *id |
| << "."; |
| } |
| auto unit = units_match->nodesetval->nodeTab[0]; |
| auto unit_name = common_parser.GetAttrValue(unit, "name"); |
| if (!unit_name) { |
| LOG(INFO) << "No unit name for TELC:datatype " << *id << "."; |
| unit_map[*id] = ""; |
| continue; |
| } |
| auto unit_symbol = |
| common_parser.GetXPathNodeTextValue(unit, "./TELC:symbol"); |
| unit_map[*id] = *unit_name + " (" + unit_symbol.value_or("-") + ")"; |
| } |
| } |
| |
| // Now a final pass to extract sample extraction and transformation rules. |
| |
| // Maps to track the encountered samples and their position in the result. |
| // This is later used for extra parameters handling. |
| unordered_map<string, size_t> sample_name_map, extra_arg_map; |
| // Indexes to values for every ctx_.extra_args_ entry. This is needed as we |
| // build-up the result table while recording extra_args_ data, so pointers |
| // will change. |
| vector<size_t> extra_arg_indexes; |
| for (const auto& guid : guids) { |
| // GUID-local sample index. |
| size_t guid_sample_idx = 0; |
| // Data offset in bytes. |
| size_t data_offset = 0; |
| // Set of metadata files for the current GUID. |
| auto& metadata_files = supported_guids[guid]; |
| // Each parsed file will only be parsed once, so keep the parser within loop |
| // scope. |
| xml::XmlParser agg_parser; |
| xml::XmlParser agg_intf_parser; |
| |
| // Setup parsers for aggregator, aggregator interface and common files. |
| int result = agg_parser.ParseFile(metadata_files.aggregator_); |
| if (result) { |
| AGG_XML_ERROR_EXIT(metadata_files.aggregator_ << ": " |
| << strerror(result)); |
| } |
| result = agg_intf_parser.ParseFile(metadata_files.aggregator_interface_); |
| if (result) { |
| AGG_XML_ERROR_EXIT(metadata_files.aggregator_interface_ |
| << ": " << strerror(result)); |
| } |
| |
| agg_parser.RegisterNamespace(kXsiNs, kXsiNsUri); |
| agg_parser.RegisterNamespace(kXiNs, kXiNsUri); |
| agg_parser.RegisterNamespace(kTELCNs, kTELCNsUri); |
| agg_parser.RegisterNamespace(kTELEMNs, kTELEMNsUri); |
| agg_intf_parser.RegisterNamespace(kCommonNs, kCommonNsUri); |
| agg_intf_parser.RegisterNamespace(kXiNs, kXiNsUri); |
| agg_intf_parser.RegisterNamespace(kTELINs, kTELINsUri); |
| |
| xml::ScopedXmlXPathObject samples_match = |
| agg_parser.XPathEval(kXPathSamples); |
| if (!samples_match || !samples_match->nodesetval || |
| !samples_match->nodesetval->nodeTab) { |
| AGG_XML_ERROR_EXIT("failed to find " << kXPathSamples); |
| } |
| |
| xmlNodeSetPtr samples = samples_match->nodesetval; |
| // Iterate over samples defined in the aggregator. For every sample decode |
| // extraction parameters. If it's not a placeholder sample to skip, |
| // correlate it with the aggregator interface based on the sample index and |
| // extract the transformation parameters. |
| // Note that both files seem to be ordered by the sample index so in |
| // theory one could just iterate both files in parallel and do this in O(n) |
| // but it's safer to correlate both files (though we end up with O(n**2)). |
| |
| // Keep track of the current sample group to figure out when to switch the |
| // data offset to a new word. |
| auto current_group = samples->nodeTab[0]->parent; |
| for (size_t i = 0; i < samples->nodeNr; i++, guid_sample_idx++) { |
| auto sample = samples->nodeTab[i]; |
| |
| // Parse the extraction parameters. |
| auto sample_id(agg_parser.GetAttrValue(sample, "sampleID")); |
| if (!sample_id) { |
| AGG_XML_ERROR_EXIT("failed to parse GUID 0x" |
| << hex << guid << " sample nr " << guid_sample_idx); |
| } |
| auto sample_name(agg_parser.GetAttrValue(sample, "name")); |
| if (!sample_name) { |
| AGG_XML_ERROR_EXIT("failed to parse GUID 0x" |
| << hex << guid << " sample nr " << guid_sample_idx); |
| } |
| auto sample_unit_id(agg_parser.GetAttrValue(sample, "datatypeIDREF")); |
| if (!sample_unit_id) { |
| AGG_XML_ERROR_EXIT("failed to parse GUID 0x" |
| << hex << guid << " sample nr " << guid_sample_idx); |
| } |
| if (!unit_map.contains(*sample_unit_id)) { |
| AGG_XML_ERROR_EXIT("failed to find unit for GUID 0x" |
| << hex << guid << " sample nr " << guid_sample_idx); |
| } |
| auto lsb_str(agg_parser.GetXPathNodeTextValue(sample, kXPathLsb)); |
| auto msb_str(agg_parser.GetXPathNodeTextValue(sample, kXPathMsb)); |
| if (!lsb_str || !msb_str) { |
| AGG_XML_ERROR_EXIT("failed to find lsb and msb fields for GUID 0x" |
| << hex << guid << " sample " << *sample_id); |
| } |
| |
| struct SampleDecodingInfo info = {0}; |
| unsigned int integer; |
| if (!base::StringToUint(*lsb_str, &integer)) { |
| AGG_XML_ERROR_EXIT("failed to parse GUID 0x" << hex << guid |
| << " sample " << *sample_id |
| << " lsb: " << *lsb_str); |
| } |
| info.lsb_ = integer; |
| if (!base::StringToUint(*msb_str, &integer)) { |
| AGG_XML_ERROR_EXIT("failed to parse GUID 0x" << hex << guid |
| << " sample " << *sample_id |
| << " msb: " << *msb_str); |
| } |
| info.msb_ = integer; |
| |
| // If sample group changed adjust the data offset. |
| if (current_group != sample->parent) { |
| data_offset += 8; |
| current_group = sample->parent; |
| } |
| // Set the offset to the beginning of the current 64bit word. |
| info.offset_ = data_offset; |
| |
| // If this sample should be skipped, do so. The offset was updated |
| // already. |
| if (RE2::PartialMatch(*sample_id, samples_to_skip)) { |
| continue; |
| } |
| |
| // Find the corresponding aggregate interface definition. |
| string xpath_sample_intf_id = |
| "/TELI:AggregatorInterface/TELI:AggregatorSamples/" |
| "TELI:T_AggregatorSample[@sampleID='"; |
| xpath_sample_intf_id += base::ToString(guid_sample_idx); |
| xpath_sample_intf_id += "']"; |
| xml::ScopedXmlXPathObject sample_intf_match = |
| agg_intf_parser.XPathEval(xpath_sample_intf_id); |
| |
| if (!sample_intf_match || !sample_intf_match->nodesetval || |
| sample_intf_match->nodesetval->nodeNr != 1) { |
| AGG_XML_ERROR_EXIT("Failed to find aggregator interface for GUID 0x" |
| << hex << guid << " sample " << *sample_id); |
| } |
| auto sample_intf = sample_intf_match->nodesetval->nodeTab[0]; |
| |
| // Safety check: TELC:sample.name == TELI:T_AggregatorSample.sampleName. |
| auto sample_intf_name = |
| agg_intf_parser.GetAttrValue(sample_intf, "sampleName"); |
| if (!sample_intf_name || *sample_intf_name != sample_name) { |
| AGG_XML_ERROR_EXIT("aggregator interface for GUID 0x" |
| << hex << guid << " sample " << *sample_name |
| << " does not match: " << *sample_intf_name); |
| } |
| |
| // Find and fill transformation parameters. |
| auto transform_ref = agg_intf_parser.GetXPathNodeTextValue( |
| sample_intf, kXPathTransformRef); |
| if (!transform_ref) { |
| AGG_XML_ERROR_EXIT("failed to find transformation type for GUID 0x" |
| << hex << guid << " sample " << *sample_id); |
| } |
| |
| if (!transform_map.contains(*transform_ref)) { |
| AGG_XML_ERROR_EXIT("unknown transformation " << *transform_ref); |
| } |
| auto data_type = transform_map[*transform_ref]; |
| if (data_type == DataType::FLOAT) { |
| info.transform_.to_float_ = GetFloatTransform(*transform_ref); |
| if (info.transform_.to_float_ == nullptr) { |
| LOG(WARNING) << "No known transformation for GUID 0x" << hex << guid |
| << " sample " << *sample_id << ". Skipping."; |
| continue; |
| } |
| } else { |
| info.transform_.to_int_ = GetIntegerTransform(*transform_ref); |
| if (info.transform_.to_int_ == nullptr) { |
| LOG(WARNING) << "No known transformation for GUID 0x" << hex << guid |
| << " sample " << *sample_id << ". Skipping."; |
| continue; |
| } |
| } |
| |
| // Check transformation parameters or find other parameters if necessary. |
| auto parameters_match = agg_intf_parser.XPathNodeEval( |
| sample_intf, |
| "./cmn:TransFormInputs/cmn:TransFormInput/cmn:sampleIDREF"); |
| if (!parameters_match || !parameters_match->nodesetval || |
| parameters_match->nodesetval->nodeNr < 1) { |
| AGG_XML_ERROR_EXIT("invalid number of parameters for GUID 0x" |
| << hex << guid << " sample " << *sample_id); |
| } |
| for (int param_idx = 0; param_idx < parameters_match->nodesetval->nodeNr; |
| param_idx++) { |
| auto param = parameters_match->nodesetval->nodeTab[param_idx]; |
| // This should never happen (nodeNr > 0), it means an error in |
| // libxml2 or the metadata schema changed drastically. |
| if (!param || !param->children || !param->children->content) { |
| AGG_XML_ERROR_EXIT("error in libxml child parsing for GUID 0x" |
| << hex << guid << " sample " << *sample_id); |
| } |
| string param_name(xml::XmlCharCast(param->children->content)); |
| if (param_idx == 0) { |
| // The first parameter in all supported transformations is the |
| // sample. Make sure it is so. |
| if (param_name != sample_id) { |
| AGG_XML_ERROR_EXIT("first parameter of GUID 0x" |
| << hex << guid << " sample " << *sample_id |
| << " is not the sample: " << param_name); |
| } |
| // Now handle 2 special cases of a single-parameter transformation |
| // with an implicit parameter. |
| if (transform_ref == "pkgc_wake_cause") { |
| // pkgc_wake_cause is a special case that in fact is a 2-parameter |
| // transformation, with the PACKAGE_CSTATE_WAKE_REFCNT as the 2nd |
| // parameter implicit in some metadata files and not in others. |
| if (!sample_name_map.contains(kSamplePkgcWakeRefcnt)) { |
| AGG_XML_ERROR_EXIT( |
| "failed to setup pkgc_wake_cause transformation, " |
| << kSamplePkgcWakeRefcnt << " missing."); |
| } |
| size_t sample_idx = sample_name_map[kSamplePkgcWakeRefcnt]; |
| if (!extra_arg_map.contains(kSamplePkgcWakeRefcnt)) { |
| extra_arg_map[kSamplePkgcWakeRefcnt] = ctx_.extra_args_.size(); |
| // Push empty for now, we'll update it with pointers later. |
| ctx_.extra_args_.push_back({}); |
| extra_arg_indexes.push_back(sample_idx); |
| } |
| info.extra_arg_idx_ = extra_arg_map[kSamplePkgcWakeRefcnt]; |
| break; |
| } else if (transform_ref == "pkgc_block_cause") { |
| // pkgc_block_cause is a special case that in fact is a 2-parameter |
| // transformation, with the PACKAGE_CSTATE_BLOCK_REFCNT 2nd |
| // parameter implicit in some metadata and not in others. |
| if (!sample_name_map.contains(kSamplePkgcBlockRefcnt)) { |
| AGG_XML_ERROR_EXIT( |
| "failed to setup pkgc_wake_cause transformation, " |
| << kSamplePkgcBlockRefcnt << " missing."); |
| } |
| size_t sample_idx = sample_name_map[kSamplePkgcBlockRefcnt]; |
| if (!extra_arg_map.contains(kSamplePkgcBlockRefcnt)) { |
| extra_arg_map[kSamplePkgcBlockRefcnt] = ctx_.extra_args_.size(); |
| // Push an empty struct, pointers will be updated later. |
| ctx_.extra_args_.push_back({}); |
| extra_arg_indexes.push_back(sample_idx); |
| } |
| info.extra_arg_idx_ = extra_arg_map[kSamplePkgcBlockRefcnt]; |
| break; |
| } |
| } else if (param_idx == 1) { |
| if (!sample_name_map.contains(param_name)) { |
| AGG_XML_ERROR_EXIT("failed to setup " << *transform_ref |
| << " transformation, " |
| << param_name << " missing."); |
| } |
| size_t sample_idx = sample_name_map[param_name]; |
| if (!extra_arg_map.contains(param_name)) { |
| extra_arg_map[param_name] = ctx_.extra_args_.size(); |
| // Push an empty struct, pointers will be updated later. |
| ctx_.extra_args_.push_back({}); |
| extra_arg_indexes.push_back(sample_idx); |
| } |
| info.extra_arg_idx_ = extra_arg_map[param_name]; |
| } else { |
| AGG_XML_ERROR_EXIT("invalid number of parameters for GUID 0x" |
| << hex << guid << " sample " << *sample_id); |
| } |
| } |
| |
| // Fill in the metadata. |
| SampleMetadata metadata{ |
| .name_ = string(*sample_id), |
| .group_ = agg_parser.GetXPathNodeTextValue(sample, kXPathSubgroup) |
| .value_or(""), |
| .description_ = |
| agg_parser.GetXPathNodeTextValue(sample, kXPathDescription) |
| .value_or(""), |
| .unit_ = unit_map[*sample_unit_id], |
| .type_ = data_type, |
| .guid_ = guid, |
| }; |
| |
| // Commit the new sample. |
| sample_name_map[metadata.name_] = ctx_.info_.size(); |
| ctx_.info_.push_back(info); |
| ctx_.result_.meta_.push_back(metadata); |
| ctx_.result_.values_.push_back(SampleValue()); |
| } |
| // Now that the vectors are set, update the extra_args pointers. |
| for (int i = 0; i < extra_arg_indexes.size(); i++) { |
| ctx_.extra_args_[i].parameter_1_ = |
| &ctx_.result_.values_[extra_arg_indexes[i]]; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int PmtDecoder::CleanUpDecoding() { |
| if (ctx_.info_.empty()) { |
| return -ENOENT; |
| } |
| ctx_.extra_args_.clear(); |
| ctx_.info_.clear(); |
| ctx_.result_.meta_.clear(); |
| ctx_.result_.values_.clear(); |
| return 0; |
| } |
| |
| const DecodingResult* PmtDecoder::Decode(const Snapshot* const data) { |
| const size_t nvals = ctx_.result_.values_.size(); |
| Guid guid = 0; |
| const char* pmt_data = nullptr; |
| size_t pmt_data_size = 0; |
| // Iterate over ctx_.info_ while keeping track of the current GUID. |
| for (int i = 0; i < nvals; i++) { |
| const auto& meta = ctx_.result_.meta_[i]; |
| const auto& info = ctx_.info_[i]; |
| auto& value = ctx_.result_.values_[i]; |
| // When moving to a new GUID, switch the data pointer. |
| if (guid != meta.guid_) [[unlikely]] { |
| pmt_data = nullptr; |
| for (const auto& device : data->devices()) { |
| if (device.guid() == meta.guid_) { |
| pmt_data = device.data().c_str(); |
| pmt_data_size = device.data().size(); |
| break; |
| } |
| } |
| // There is an edge case where user set up collection for a different |
| // set of GUIDs than decoding. It's better to error out in that case |
| // instead of silently skipping all the samples for that GUID. |
| if (pmt_data == nullptr) [[unlikely]] { |
| LOG(ERROR) << "GUID 0x" << hex << meta.guid_ |
| << " is not present in the PMT snapshot."; |
| return nullptr; |
| } |
| guid = meta.guid_; |
| } |
| // Since PMT data buffer is read from sysfs, this can only happen if the |
| // PMT schema assumes that PMC should generate more data. This means |
| // either schema error or a need to perform a uCode update. To maintain |
| // forward compatibility: warn and skip. |
| if (info.offset_ > pmt_data_size) [[unlikely]] { |
| LOG(WARNING) << "Not enough data in PMT: " << meta.name_ |
| << " is missing (" << info.offset_ << " > " << pmt_data_size |
| << ")"; |
| continue; |
| } |
| // Extract the value. |
| memcpy(&value.u64_, pmt_data + info.offset_, sizeof(value.u64_)); |
| value.u64_ = GetField(value.u64_, info.msb_, info.lsb_); |
| // Transform the value. |
| if (meta.type_ == FLOAT) { |
| value.f_ = info.transform_.to_float_(value, &ctx_, i); |
| } else { |
| // Transform function will sign-extend the integer if needed. This means |
| // we can always assign to the i64_ field while u64_ is just a shortcut |
| // for accessing DataType::UINT. |
| value.i64_ = info.transform_.to_int_(value, &ctx_, i); |
| } |
| } |
| return &ctx_.result_; |
| } |
| |
| } // namespace pmt |