1
0
Fork 0
telegraf/plugins/inputs/intel_pmt/xml_parser.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

274 lines
7.8 KiB
Go

//go:build linux && amd64
package intel_pmt
import (
"encoding/xml"
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
type pmt struct {
XMLName xml.Name `xml:"pmt"`
Mappings mappings `xml:"mappings"`
}
type mappings struct {
XMLName xml.Name `xml:"mappings"`
Mapping []mapping `xml:"mapping"`
}
type mapping struct {
XMLName xml.Name `xml:"mapping"`
GUID string `xml:"guid,attr"`
XMLSet xmlset `xml:"xmlset"`
}
type xmlset struct {
XMLName xml.Name `xml:"xmlset"`
Basedir string `xml:"basedir"`
Aggregator string `xml:"aggregator"`
AggregatorInterface string `xml:"aggregatorinterface"`
}
type aggregator struct {
XMLName xml.Name `xml:"Aggregator"`
Name string `xml:"name"`
SampleGroup []sampleGroup `xml:"SampleGroup"`
}
type sampleGroup struct {
XMLName xml.Name `xml:"SampleGroup"`
SampleID uint64 `xml:"sampleID,attr"`
Sample []sample `xml:"sample"`
}
type sample struct {
XMLName xml.Name `xml:"sample"`
SampleName string `xml:"name,attr"`
DatatypeIDRef string `xml:"datatypeIDREF,attr"`
SampleID string `xml:"sampleID,attr"`
Lsb uint64 `xml:"lsb"`
Msb uint64 `xml:"msb"`
mask uint64
}
type aggregatorInterface struct {
XMLName xml.Name `xml:"AggregatorInterface"`
Transformations transformations `xml:"TransFormations"`
AggregatorSamples aggregatorSamples `xml:"AggregatorSamples"`
}
type transformations struct {
XMLName xml.Name `xml:"TransFormations"`
Transformation []transformation `xml:"TransFormation"`
}
type transformation struct {
XMLName xml.Name `xml:"TransFormation"`
Name string `xml:"name,attr"`
TransformID string `xml:"transformID,attr"`
Transform string `xml:"transform"`
}
type aggregatorSamples struct {
XMLName xml.Name `xml:"AggregatorSamples"`
AggregatorSample []aggregatorSample `xml:"T_AggregatorSample"`
}
type aggregatorSample struct {
XMLName xml.Name `xml:"T_AggregatorSample"`
SampleName string `xml:"sampleName,attr"`
SampleGroup string `xml:"sampleGroup,attr"`
DatatypeIDRef string `xml:"datatypeIDREF,attr"`
TransformInputs transformInputs `xml:"TransFormInputs"`
TransformREF string `xml:"transformREF"`
core string
cha string
}
type transformInputs struct {
XMLName xml.Name `xml:"TransFormInputs"`
TransformInput []transformInput `xml:"TransFormInput"`
}
type transformInput struct {
XMLName xml.Name `xml:"TransFormInput"`
VarName string `xml:"varName,attr"`
SampleIDREF string `xml:"sampleIDREF"`
}
type sourceReader interface {
getReadCloser(source string) (io.ReadCloser, error)
}
type fileReader struct{}
func (fileReader) getReadCloser(source string) (io.ReadCloser, error) {
return os.Open(source)
}
// parseXMLs reads and parses PMT XMLs.
//
// This method retrieves all metadata about known GUIDs from PmtSpec.
// Then, it explores PMT sysfs to find all readable "telem" files and their GUIDs.
// It then matches found (readable) system GUIDs with GUIDs from metadata and
// reads corresponding sets of XMLs.
//
// Returns:
//
// error - if PMT spec is empty, if exploring PMT sysfs fails, or if reading XMLs fails.
func (p *IntelPMT) parseXMLs() error {
err := parseXML(p.PmtSpec, p.reader, &p.pmtMetadata)
if err != nil {
return err
}
if len(p.pmtMetadata.Mappings.Mapping) == 0 {
return errors.New("pmt XML provided contains no mappings")
}
err = p.readXMLs()
if err != nil {
return err
}
p.pmtTransformations = make(map[string]map[string]transformation)
for guid := range p.pmtTelemetryFiles {
p.pmtTransformations[guid] = make(map[string]transformation)
for _, transform := range p.pmtAggregatorInterface[guid].Transformations.Transformation {
p.pmtTransformations[guid][transform.TransformID] = transform
}
}
return nil
}
// readXMLs function reads all XMLs for found GUIDs.
//
// This method reads two required XMLs for each found GUID,
// checks if any of the provided filtering metrics were not found,
// and checks if there is at least one non-empty XML set.
//
// Returns:
//
// error - error if reading operation failed or if all XMLs are empty.
func (p *IntelPMT) readXMLs() error {
p.pmtAggregator = make(map[string]aggregator)
p.pmtAggregatorInterface = make(map[string]aggregatorInterface)
dtMetricsFound := make(map[string]bool)
sampleFilterFound := make(map[string]bool)
for guid := range p.pmtTelemetryFiles {
err := p.getAllXMLData(guid, dtMetricsFound, sampleFilterFound)
if err != nil {
return fmt.Errorf("failed reading XMLs: %w", err)
}
}
for _, dt := range p.DatatypeFilter {
if _, ok := dtMetricsFound[dt]; !ok {
p.Log.Warnf("Configured datatype metric %q has not been found", dt)
}
}
for _, sm := range p.SampleFilter {
if _, ok := sampleFilterFound[sm]; !ok {
p.Log.Warnf("Configured sample metric %q has not been found", sm)
}
}
return p.verifyNoEmpty()
}
// getAllXMLData retrieves two XMLs for given GUID.
//
// This method reads where to find the Aggregator and Aggregator interface XMLs
// from pmt metadata and reads found XMLs.
// This method also filters read XMLs before saving them
// and extracts additional tags from the data.
//
// Parameters:
//
// guid - GUID saying which XMLs should be read.
// dtMetricsFound - a map of found datatype metrics for all GUIDs.
// smFound - a map of found sample names for all GUIDs.
//
// Returns:
//
// error - if reading XML has failed.
func (p *IntelPMT) getAllXMLData(guid string, dtMetricsFound, smFound map[string]bool) error {
for _, mapping := range p.pmtMetadata.Mappings.Mapping {
if mapping.GUID == guid {
basedir := mapping.XMLSet.Basedir
guid := mapping.GUID
var aggSource, aggInterfaceSource string
aggSource = filepath.Join(p.pmtBasePath, basedir, mapping.XMLSet.Aggregator)
aggInterfaceSource = filepath.Join(p.pmtBasePath, basedir, mapping.XMLSet.AggregatorInterface)
tAgg := aggregator{}
tAggInterface := aggregatorInterface{}
err := parseXML(aggSource, p.reader, &tAgg)
if err != nil {
return fmt.Errorf("failed reading aggregator XML: %w", err)
}
err = parseXML(aggInterfaceSource, p.reader, &tAggInterface)
if err != nil {
return fmt.Errorf("failed reading aggregator interface XML: %w", err)
}
if len(p.DatatypeFilter) > 0 {
tAgg.filterAggregatorByDatatype(p.DatatypeFilter)
tAggInterface.filterAggInterfaceByDatatype(p.DatatypeFilter, dtMetricsFound)
}
if len(p.SampleFilter) > 0 {
tAgg.filterAggregatorBySampleName(p.SampleFilter)
tAggInterface.filterAggInterfaceBySampleName(p.SampleFilter, smFound)
}
tAgg.calculateMasks()
p.pmtAggregator[guid] = tAgg
tAggInterface.extractTagsFromSample()
p.pmtAggregatorInterface[guid] = tAggInterface
}
}
return nil
}
func (a *aggregator) calculateMasks() {
for i := range a.SampleGroup {
for j, sample := range a.SampleGroup[i].Sample {
mask := computeMask(sample.Msb, sample.Lsb)
a.SampleGroup[i].Sample[j].mask = mask
}
}
}
func computeMask(msb, lsb uint64) uint64 {
msbMask := uint64(0xffffffffffffffff) & ((1 << (msb + 1)) - 1)
lsbMask := uint64(0xffffffffffffffff) & (1<<lsb - 1)
return msbMask & (^lsbMask)
}
func parseXML(source string, sr sourceReader, v interface{}) error {
if sr == nil {
return errors.New("xml reader has not been initialized")
}
reader, err := sr.getReadCloser(source)
if err != nil {
return fmt.Errorf("error reading source %q: %w", source, err)
}
defer reader.Close()
parser := xml.NewDecoder(reader)
parser.AutoClose = xml.HTMLAutoClose
parser.Entity = xml.HTMLEntity
// There are "&" in XMLs in entity references.
// Parser sees it as not allowed characters.
// Strict mode disabled to handle that.
parser.Strict = false
err = parser.Decode(v)
if err != nil {
return fmt.Errorf("error decoding an XML %q: %w", source, err)
}
return nil
}