//go:build linux && amd64 package intel_pmu import ( "errors" "fmt" "strconv" "strings" "github.com/influxdata/telegraf" ) // Maximum size of core IDs or socket IDs (8192). Based on maximum value of CPUs that linux kernel supports. const maxIDsSize = 1 << 13 type entitiesParser interface { parseEntities(coreEntities []*coreEventEntity, uncoreEntities []*uncoreEventEntity) (err error) } type configParser struct { log telegraf.Logger sys sysInfoProvider } func (cp *configParser) parseEntities(coreEntities []*coreEventEntity, uncoreEntities []*uncoreEventEntity) (err error) { if len(coreEntities) == 0 && len(uncoreEntities) == 0 { return errors.New("neither core nor uncore entities configured") } for _, coreEntity := range coreEntities { if coreEntity == nil { return errors.New("core entity is nil") } if coreEntity.Events == nil { if cp.log != nil { cp.log.Debug("all core events from provided files will be configured") } coreEntity.allEvents = true } else { events := cp.parseEvents(coreEntity.Events) if events == nil { return errors.New("an empty list of core events was provided") } coreEntity.parsedEvents = events } coreEntity.parsedCores, err = cp.parseCores(coreEntity.Cores) if err != nil { return fmt.Errorf("error during cores parsing: %w", err) } } for _, uncoreEntity := range uncoreEntities { if uncoreEntity == nil { return errors.New("uncore entity is nil") } if uncoreEntity.Events == nil { if cp.log != nil { cp.log.Debug("all uncore events from provided files will be configured") } uncoreEntity.allEvents = true } else { events := cp.parseEvents(uncoreEntity.Events) if events == nil { return errors.New("an empty list of uncore events was provided") } uncoreEntity.parsedEvents = events } uncoreEntity.parsedSockets, err = cp.parseSockets(uncoreEntity.Sockets) if err != nil { return fmt.Errorf("error during sockets parsing: %w", err) } } return nil } func (cp *configParser) parseEvents(events []string) []*eventWithQuals { if len(events) == 0 { return nil } events, duplications := removeDuplicateStrings(events) for _, duplication := range duplications { if cp.log != nil { cp.log.Warnf("duplicated event %q will be removed", duplication) } } return parseEventsWithQualifiers(events) } func (cp *configParser) parseCores(cores []string) ([]int, error) { if cores == nil { if cp.log != nil { cp.log.Debug("all possible cores will be configured") } if cp.sys == nil { return nil, errors.New("system info provider is nil") } cores, err := cp.sys.allCPUs() if err != nil { return nil, fmt.Errorf("cannot obtain all cpus: %w", err) } return cores, nil } if len(cores) == 0 { return nil, errors.New("an empty list of cores was provided") } result, err := cp.parseIntRanges(cores) if err != nil { return nil, err } return result, nil } func (cp *configParser) parseSockets(sockets []string) ([]int, error) { if sockets == nil { if cp.log != nil { cp.log.Debug("all possible sockets will be configured") } if cp.sys == nil { return nil, errors.New("system info provider is nil") } sockets, err := cp.sys.allSockets() if err != nil { return nil, fmt.Errorf("cannot obtain all sockets: %w", err) } return sockets, nil } if len(sockets) == 0 { return nil, errors.New("an empty list of sockets was provided") } result, err := cp.parseIntRanges(sockets) if err != nil { return nil, err } return result, nil } func (cp *configParser) parseIntRanges(ranges []string) ([]int, error) { var ids []int var duplicatedIDs []int var err error ids, err = parseIDs(ranges) if err != nil { return nil, err } ids, duplicatedIDs = removeDuplicateValues(ids) for _, duplication := range duplicatedIDs { if cp.log != nil { cp.log.Warnf("duplicated id number `%d` will be removed", duplication) } } return ids, nil } func parseEventsWithQualifiers(events []string) []*eventWithQuals { result := make([]*eventWithQuals, 0, len(events)) for _, event := range events { newEventWithQualifiers := &eventWithQuals{} split := strings.Split(event, ":") newEventWithQualifiers.name = split[0] if len(split) > 1 { newEventWithQualifiers.qualifiers = split[1:] } result = append(result, newEventWithQualifiers) } return result } func parseIDs(allIDsStrings []string) ([]int, error) { var result []int for _, idsString := range allIDsStrings { ids := strings.Split(idsString, ",") for _, id := range ids { id := strings.TrimSpace(id) // a-b support var start, end uint n, err := fmt.Sscanf(id, "%d-%d", &start, &end) if err == nil && n == 2 { if start >= end { return nil, fmt.Errorf("`%d` is equal or greater than `%d`", start, end) } for ; start <= end; start++ { if len(result)+1 > maxIDsSize { return nil, fmt.Errorf("requested number of IDs exceeds max size `%d`", maxIDsSize) } result = append(result, int(start)) } continue } // Single value num, err := strconv.Atoi(id) if err != nil { return nil, fmt.Errorf("wrong format for id number %q: %w", id, err) } if len(result)+1 > maxIDsSize { return nil, fmt.Errorf("requested number of IDs exceeds max size `%d`", maxIDsSize) } result = append(result, num) } } return result, nil } func removeDuplicateValues(intSlice []int) (result, duplicates []int) { keys := make(map[int]bool) for _, entry := range intSlice { if _, value := keys[entry]; !value { keys[entry] = true result = append(result, entry) } else { duplicates = append(duplicates, entry) } } return result, duplicates } func removeDuplicateStrings(strSlice []string) (result, duplicates []string) { keys := make(map[string]bool) for _, entry := range strSlice { if _, value := keys[entry]; !value { keys[entry] = true result = append(result, entry) } else { duplicates = append(duplicates, entry) } } return result, duplicates }