275 lines
7.8 KiB
Go
275 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
|
||
|
}
|