vendor: cadvisor v0.38.4

This commit is contained in:
David Porter
2020-11-13 19:52:57 +00:00
parent ec734aced7
commit 8af7405f17
396 changed files with 73154 additions and 18510 deletions

View File

@@ -219,7 +219,7 @@ func readUInt64(dirpath string, file string) uint64 {
// Lists all directories under "path" and outputs the results as children of "parent".
func ListDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}) error {
buf := make([]byte, godirwalk.DefaultScratchBufferSize)
buf := make([]byte, godirwalk.MinimumScratchBufferSize)
return listDirectories(dirpath, parent, recursive, output, buf)
}

View File

@@ -52,11 +52,10 @@ func Client() (*dclient.Client, error) {
TLSClientConfig: tlsc,
}
}
dockerClient, dockerClientErr = dclient.NewClient(*ArgDockerEndpoint,
"",
client,
nil)
dockerClient, dockerClientErr = dclient.NewClientWithOpts(
dclient.WithHost(*ArgDockerEndpoint),
dclient.WithHTTPClient(client),
dclient.WithAPIVersionNegotiation())
})
return dockerClient, dockerClientErr
}

View File

@@ -47,6 +47,7 @@ const (
ProcessSchedulerMetrics MetricKind = "sched"
PerCpuUsageMetrics MetricKind = "percpu"
MemoryUsageMetrics MetricKind = "memory"
MemoryNumaMetrics MetricKind = "memory_numa"
CpuLoadMetrics MetricKind = "cpuLoad"
DiskIOMetrics MetricKind = "diskIO"
DiskUsageMetrics MetricKind = "disk"
@@ -70,6 +71,7 @@ var AllMetrics = MetricSet{
ProcessSchedulerMetrics: struct{}{},
PerCpuUsageMetrics: struct{}{},
MemoryUsageMetrics: struct{}{},
MemoryNumaMetrics: struct{}{},
CpuLoadMetrics: struct{}{},
DiskIOMetrics: struct{}{},
AcceleratorUsageMetrics: struct{}{},

View File

@@ -870,6 +870,24 @@ func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.WorkingSet = workingSet
}
func getNumaStats(memoryStats map[uint8]uint64) map[uint8]uint64 {
stats := make(map[uint8]uint64, len(memoryStats))
for node, usage := range memoryStats {
stats[node] = usage
}
return stats
}
func setMemoryNumaStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.ContainerData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.File.Nodes)
ret.Memory.ContainerData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Anon.Nodes)
ret.Memory.ContainerData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Unevictable.Nodes)
ret.Memory.HierarchicalData.NumaStats.File = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.File.Nodes)
ret.Memory.HierarchicalData.NumaStats.Anon = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Anon.Nodes)
ret.Memory.HierarchicalData.NumaStats.Unevictable = getNumaStats(s.MemoryStats.PageUsageByNUMA.Hierarchical.Unevictable.Nodes)
}
func setHugepageStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Hugetlb = make(map[string]info.HugetlbStats)
for k, v := range s.HugetlbStats {
@@ -923,6 +941,9 @@ func newContainerStats(libcontainerStats *libcontainer.Stats, includedMetrics co
setDiskIoStats(s, ret)
}
setMemoryStats(s, ret)
if includedMetrics.Has(container.MemoryNumaMetrics) {
setMemoryNumaStats(s, ret)
}
if includedMetrics.Has(container.HugetlbUsageMetrics) {
setHugepageStats(s, ret)
}

View File

@@ -70,11 +70,19 @@ func NewRawContainerWatcher() (watcher.ContainerWatcher, error) {
func (w *rawContainerWatcher) Start(events chan watcher.ContainerEvent) error {
// Watch this container (all its cgroups) and all subdirectories.
watched := make([]string, 0)
for _, cgroupPath := range w.cgroupPaths {
_, err := w.watchDirectory(events, cgroupPath, "/")
if err != nil {
for _, watchedCgroupPath := range watched {
_, removeErr := w.watcher.RemoveWatch("/", watchedCgroupPath)
if removeErr != nil {
klog.Warningf("Failed to remove inotify watch for %q with error: %v", watchedCgroupPath, removeErr)
}
}
return err
}
watched = append(watched, cgroupPath)
}
// Process the events received from the kernel.

View File

@@ -399,9 +399,16 @@ type MemoryStats struct {
HierarchicalData MemoryStatsMemoryData `json:"hierarchical_data,omitempty"`
}
type MemoryNumaStats struct {
File map[uint8]uint64 `json:"file,omitempty"`
Anon map[uint8]uint64 `json:"anon,omitempty"`
Unevictable map[uint8]uint64 `json:"unevictable,omitempty"`
}
type MemoryStatsMemoryData struct {
Pgfault uint64 `json:"pgfault"`
Pgmajfault uint64 `json:"pgmajfault"`
Pgfault uint64 `json:"pgfault"`
Pgmajfault uint64 `json:"pgmajfault"`
NumaStats MemoryNumaStats `json:"numa_stats,omitempty"`
}
type InterfaceStats struct {
@@ -827,6 +834,13 @@ type AcceleratorStats struct {
// PerfStat represents value of a single monitored perf event.
type PerfStat struct {
PerfValue
// CPU that perf event was measured on.
Cpu int `json:"cpu"`
}
type PerfValue struct {
// Indicates scaling ratio for an event: time_running/time_enabled
// (amount of time that event was being measured divided by
// amount of time that event was enabled for).
@@ -843,9 +857,6 @@ type PerfStat struct {
// Name is human readable name of an event.
Name string `json:"name"`
// CPU that perf event was measured on.
Cpu int `json:"cpu"`
}
// MemoryBandwidthStats corresponds to MBM (Memory Bandwidth Monitoring).
@@ -876,22 +887,7 @@ type ResctrlStats struct {
// PerfUncoreStat represents value of a single monitored perf uncore event.
type PerfUncoreStat struct {
// Indicates scaling ratio for an event: time_running/time_enabled
// (amount of time that event was being measured divided by
// amount of time that event was enabled for).
// value 1.0 indicates that no multiplexing occurred. Value close
// to 0 indicates that event was measured for short time and event's
// value might be inaccurate.
// See: https://lwn.net/Articles/324756/
ScalingRatio float64 `json:"scaling_ratio"`
// Value represents value of perf event retrieved from OS. It is
// normalized against ScalingRatio and takes multiplexing into
// consideration.
Value uint64 `json:"value"`
// Name is human readable name of an event.
Name string `json:"name"`
PerfValue
// Socket that perf event was measured on.
Socket int `json:"socket"`

View File

@@ -87,6 +87,7 @@ func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes {
MemoryCapacity: mi.MemoryCapacity,
MachineID: mi.MachineID,
SystemUUID: mi.SystemUUID,
HugePages: mi.HugePages,
Filesystems: mi.Filesystems,
DiskMap: mi.DiskMap,
NetworkDevices: mi.NetworkDevices,

View File

@@ -90,11 +90,6 @@ func GetSockets(procInfo []byte) int {
// GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
func GetClockSpeed(procInfo []byte) (uint64, error) {
// s390/s390x, mips64, riscv64, aarch64 and arm32 changes
if isMips64() || isSystemZ() || isAArch64() || isArm32() || isRiscv64() {
return 0, nil
}
// First look through sys to find a max supported cpu frequency.
if utils.FileExists(maxFreqFile) {
val, err := ioutil.ReadFile(maxFreqFile)
@@ -108,6 +103,11 @@ func GetClockSpeed(procInfo []byte) (uint64, error) {
}
return maxFreq, nil
}
// s390/s390x, mips64, riscv64, aarch64 and arm32 changes
if isMips64() || isSystemZ() || isAArch64() || isArm32() || isRiscv64() {
return 0, nil
}
// Fall back to /proc/cpuinfo
matches := cpuClockSpeedMHz.FindSubmatch(procInfo)
if len(matches) != 2 {

View File

@@ -212,7 +212,7 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, houskeepingConfig
newManager.machineInfo = *machineInfo
klog.V(1).Infof("Machine: %+v", newManager.machineInfo)
newManager.perfManager, err = perf.NewManager(perfEventsFile, machineInfo.NumCores, machineInfo.Topology)
newManager.perfManager, err = perf.NewManager(perfEventsFile, machineInfo.Topology)
if err != nil {
return nil, err
}
@@ -932,7 +932,7 @@ func (m *manager) createContainerLocked(containerName string, watchSource watche
perfCgroupPath := path.Join(fs2.UnifiedMountpoint, containerName)
cont.perfCollector, err = m.perfManager.GetCollector(perfCgroupPath)
if err != nil {
klog.V(4).Infof("perf_event metrics will not be available for container %s: %s", containerName, err)
klog.Errorf("Perf event metrics will not be available for container %q: %v", containerName, err)
}
} else {
devicesCgroupPath, err := handler.GetCgroupPath("devices")
@@ -950,7 +950,7 @@ func (m *manager) createContainerLocked(containerName string, watchSource watche
} else {
cont.perfCollector, err = m.perfManager.GetCollector(perfCgroupPath)
if err != nil {
klog.V(4).Infof("perf_event metrics will not be available for container %s: %s", containerName, err)
klog.Errorf("Perf event metrics will not be available for container %q: %v", containerName, err)
}
}
}
@@ -1137,11 +1137,19 @@ func (m *manager) detectSubcontainers(containerName string) error {
// Watches for new containers started in the system. Runs forever unless there is a setup error.
func (m *manager) watchForNewContainers(quit chan error) error {
watched := make([]watcher.ContainerWatcher, 0)
for _, watcher := range m.containerWatchers {
err := watcher.Start(m.eventsChannel)
if err != nil {
for _, w := range watched {
stopErr := w.Stop()
if stopErr != nil {
klog.Warningf("Failed to stop wacher %v with error: %v", w, stopErr)
}
}
return err
}
watched = append(watched, watcher)
}
// There is a race between starting the watch and new container creation so we do a detection before we read new containers.

View File

@@ -422,7 +422,8 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc, includedMetri
getValues: func(s *info.ContainerStats) metricValues {
return metricValues{{value: float64(s.Memory.WorkingSet), timestamp: s.Timestamp}}
},
}, {
},
{
name: "container_memory_failures_total",
help: "Cumulative count of memory allocation failures.",
valueType: prometheus.CounterValue,
@@ -454,6 +455,33 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc, includedMetri
},
}...)
}
if includedMetrics.Has(container.MemoryNumaMetrics) {
c.containerMetrics = append(c.containerMetrics, []containerMetric{
{
name: "container_memory_numa_pages",
help: "Number of used pages per NUMA node",
valueType: prometheus.GaugeValue,
extraLabels: []string{"type", "scope", "node"},
getValues: func(s *info.ContainerStats) metricValues {
values := make(metricValues, 0)
values = append(values, getNumaStatsPerNode(s.Memory.ContainerData.NumaStats.File,
[]string{"file", "container"}, s.Timestamp)...)
values = append(values, getNumaStatsPerNode(s.Memory.ContainerData.NumaStats.Anon,
[]string{"anon", "container"}, s.Timestamp)...)
values = append(values, getNumaStatsPerNode(s.Memory.ContainerData.NumaStats.Unevictable,
[]string{"unevictable", "container"}, s.Timestamp)...)
values = append(values, getNumaStatsPerNode(s.Memory.HierarchicalData.NumaStats.File,
[]string{"file", "hierarchy"}, s.Timestamp)...)
values = append(values, getNumaStatsPerNode(s.Memory.HierarchicalData.NumaStats.Anon,
[]string{"anon", "hierarchy"}, s.Timestamp)...)
values = append(values, getNumaStatsPerNode(s.Memory.HierarchicalData.NumaStats.Unevictable,
[]string{"unevictable", "hierarchy"}, s.Timestamp)...)
return values
},
},
}...)
}
if includedMetrics.Has(container.AcceleratorUsageMetrics) {
c.containerMetrics = append(c.containerMetrics, []containerMetric{
{
@@ -1549,41 +1577,48 @@ func NewPrometheusCollector(i infoProvider, f ContainerLabelsFunc, includedMetri
}...)
}
if includedMetrics.Has(container.PerfMetrics) {
if includedMetrics.Has(container.PerCpuUsageMetrics) {
c.containerMetrics = append(c.containerMetrics, []containerMetric{
{
name: "container_perf_events_total",
help: "Perf event metric.",
valueType: prometheus.CounterValue,
extraLabels: []string{"cpu", "event"},
getValues: func(s *info.ContainerStats) metricValues {
return getPerCPUCorePerfEvents(s)
},
},
{
name: "container_perf_events_scaling_ratio",
help: "Perf event metric scaling ratio.",
valueType: prometheus.GaugeValue,
extraLabels: []string{"cpu", "event"},
getValues: func(s *info.ContainerStats) metricValues {
return getPerCPUCoreScalingRatio(s)
},
}}...)
} else {
c.containerMetrics = append(c.containerMetrics, []containerMetric{
{
name: "container_perf_events_total",
help: "Perf event metric.",
valueType: prometheus.CounterValue,
extraLabels: []string{"cpu", "event"},
getValues: func(s *info.ContainerStats) metricValues {
return getAggregatedCorePerfEvents(s)
},
},
{
name: "container_perf_events_scaling_ratio",
help: "Perf event metric scaling ratio.",
valueType: prometheus.GaugeValue,
extraLabels: []string{"cpu", "event"},
getValues: func(s *info.ContainerStats) metricValues {
return getMinCoreScalingRatio(s)
},
}}...)
}
c.containerMetrics = append(c.containerMetrics, []containerMetric{
{
name: "container_perf_events_total",
help: "Perf event metric.",
valueType: prometheus.CounterValue,
extraLabels: []string{"cpu", "event"},
getValues: func(s *info.ContainerStats) metricValues {
values := make(metricValues, 0, len(s.PerfStats))
for _, metric := range s.PerfStats {
values = append(values, metricValue{
value: float64(metric.Value),
labels: []string{strconv.Itoa(metric.Cpu), metric.Name},
timestamp: s.Timestamp,
})
}
return values
},
},
{
name: "container_perf_events_scaling_ratio",
help: "Perf event metric scaling ratio.",
valueType: prometheus.GaugeValue,
extraLabels: []string{"cpu", "event"},
getValues: func(s *info.ContainerStats) metricValues {
values := make(metricValues, 0, len(s.PerfStats))
for _, metric := range s.PerfStats {
values = append(values, metricValue{
value: metric.ScalingRatio,
labels: []string{strconv.Itoa(metric.Cpu), metric.Name},
timestamp: s.Timestamp,
})
}
return values
},
},
{
name: "container_perf_uncore_events_total",
help: "Perf uncore event metric.",
@@ -1903,3 +1938,79 @@ var invalidNameCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
func sanitizeLabelName(name string) string {
return invalidNameCharRE.ReplaceAllString(name, "_")
}
func getNumaStatsPerNode(nodeStats map[uint8]uint64, labels []string, timestamp time.Time) metricValues {
mValues := make(metricValues, 0, len(nodeStats))
for node, stat := range nodeStats {
nodeLabels := append(labels, strconv.FormatUint(uint64(node), 10))
mValues = append(mValues, metricValue{value: float64(stat), labels: nodeLabels, timestamp: timestamp})
}
return mValues
}
func getPerCPUCorePerfEvents(s *info.ContainerStats) metricValues {
values := make(metricValues, 0, len(s.PerfStats))
for _, metric := range s.PerfStats {
values = append(values, metricValue{
value: float64(metric.Value),
labels: []string{strconv.Itoa(metric.Cpu), metric.Name},
timestamp: s.Timestamp,
})
}
return values
}
func getPerCPUCoreScalingRatio(s *info.ContainerStats) metricValues {
values := make(metricValues, 0, len(s.PerfStats))
for _, metric := range s.PerfStats {
values = append(values, metricValue{
value: metric.ScalingRatio,
labels: []string{strconv.Itoa(metric.Cpu), metric.Name},
timestamp: s.Timestamp,
})
}
return values
}
func getAggregatedCorePerfEvents(s *info.ContainerStats) metricValues {
values := make(metricValues, 0)
perfEventStatAgg := make(map[string]uint64)
// aggregate by event
for _, perfStat := range s.PerfStats {
perfEventStatAgg[perfStat.Name] += perfStat.Value
}
// create aggregated metrics
for perfEvent, perfValue := range perfEventStatAgg {
values = append(values, metricValue{
value: float64(perfValue),
labels: []string{"", perfEvent},
timestamp: s.Timestamp,
})
}
return values
}
func getMinCoreScalingRatio(s *info.ContainerStats) metricValues {
values := make(metricValues, 0)
perfEventStatMin := make(map[string]float64)
// search for minimal value of scalin ratio for specific event
for _, perfStat := range s.PerfStats {
if _, ok := perfEventStatMin[perfStat.Name]; !ok {
// found a new event
perfEventStatMin[perfStat.Name] = perfStat.ScalingRatio
} else if perfStat.ScalingRatio < perfEventStatMin[perfStat.Name] {
// found a lower value of scaling ration so replace the minimal value
perfEventStatMin[perfStat.Name] = perfStat.ScalingRatio
}
}
for perfEvent, perfScalingRatio := range perfEventStatMin {
values = append(values, metricValue{
value: perfScalingRatio,
labels: []string{"", perfEvent},
timestamp: s.Timestamp,
})
}
return values
}

View File

@@ -327,10 +327,20 @@ func (p testSubcontainersInfoProvider) GetRequestedContainersInfo(string, v2.Req
ContainerData: info.MemoryStatsMemoryData{
Pgfault: 10,
Pgmajfault: 11,
NumaStats: info.MemoryNumaStats{
File: map[uint8]uint64{0: 16649, 1: 10000},
Anon: map[uint8]uint64{0: 10000, 1: 7109},
Unevictable: map[uint8]uint64{0: 8900, 1: 10000},
},
},
HierarchicalData: info.MemoryStatsMemoryData{
Pgfault: 12,
Pgmajfault: 13,
NumaStats: info.MemoryNumaStats{
File: map[uint8]uint64{0: 36649, 1: 10000},
Anon: map[uint8]uint64{0: 20000, 1: 7109},
Unevictable: map[uint8]uint64{0: 8900, 1: 20000},
},
},
Cache: 14,
RSS: 15,
@@ -625,44 +635,56 @@ func (p testSubcontainersInfoProvider) GetRequestedContainersInfo(string, v2.Req
},
PerfStats: []info.PerfStat{
{
ScalingRatio: 1.0,
Value: 123,
Name: "instructions",
Cpu: 0,
PerfValue: info.PerfValue{
ScalingRatio: 1.0,
Value: 123,
Name: "instructions",
},
Cpu: 0,
},
{
ScalingRatio: 0.5,
Value: 456,
Name: "instructions",
Cpu: 1,
PerfValue: info.PerfValue{
ScalingRatio: 0.5,
Value: 456,
Name: "instructions",
},
Cpu: 1,
},
{
ScalingRatio: 0.66666666666,
Value: 321,
Name: "instructions_retired",
Cpu: 0,
PerfValue: info.PerfValue{
ScalingRatio: 0.66666666666,
Value: 321,
Name: "instructions_retired",
},
Cpu: 0,
},
{
ScalingRatio: 0.33333333333,
Value: 789,
Name: "instructions_retired",
Cpu: 1,
PerfValue: info.PerfValue{
ScalingRatio: 0.33333333333,
Value: 789,
Name: "instructions_retired",
},
Cpu: 1,
},
},
PerfUncoreStats: []info.PerfUncoreStat{
{
ScalingRatio: 1.0,
Value: 1231231512.0,
Name: "cas_count_read",
Socket: 0,
PMU: "uncore_imc_0",
PerfValue: info.PerfValue{
ScalingRatio: 1.0,
Value: 1231231512.0,
Name: "cas_count_read",
},
Socket: 0,
PMU: "uncore_imc_0",
},
{
ScalingRatio: 1.0,
Value: 1111231331.0,
Name: "cas_count_read",
Socket: 1,
PMU: "uncore_imc_0",
PerfValue: info.PerfValue{
ScalingRatio: 1.0,
Value: 1111231331.0,
Name: "cas_count_read",
},
Socket: 1,
PMU: "uncore_imc_0",
},
},
ReferencedMemory: 1234,

View File

@@ -40,6 +40,7 @@ func init() {
// Unfortunately klog does not seem to work here. I believe it's better to
// output information using fmt rather then let it disappear silently.
fmt.Printf("libipmctl initialization failed with status %d", cErr)
return
}
isNVMLibInitialized = true
}
@@ -57,6 +58,11 @@ func getAvgPowerBudget() (uint, error) {
return uint(0), fmt.Errorf("Unable to get number of NVM devices. Status code: %d", err)
}
if count == 0 {
klog.Warningf("There are no NVM devices!")
return uint(0), nil
}
// Load basic device information for all the devices
// to obtain UID of the first one.
devices := make([]C.struct_device_discovery, count)
@@ -97,7 +103,7 @@ func GetInfo() (info.NVMInfo, error) {
nvmInfo := info.NVMInfo{}
if !isNVMLibInitialized {
klog.V(1).Info("libimpctl has not been initialized. NVM information will not be available")
klog.V(1).Info("libipmctl has not been initialized. NVM information will not be available")
return nvmInfo, nil
}

View File

@@ -30,5 +30,5 @@ func GetInfo() (info.NVMInfo, error) {
// Finalize un-initializes libipmctl. See https://github.com/google/cadvisor/issues/2457.
// When libipmctl is not available it just logs that it's being called.
func Finalize() {
klog.V(4).Info("libimpctl not available, doing nothing.")
klog.V(4).Info("libipmctl not available, doing nothing.")
}

View File

@@ -21,6 +21,7 @@ package perf
// #cgo LDFLAGS: -lpfm
// #include <perfmon/pfmlib.h>
// #include <stdlib.h>
// #include <string.h>
import "C"
import (
@@ -41,33 +42,42 @@ import (
type collector struct {
cgroupPath string
events PerfEvents
cpuFiles map[string]map[int]readerCloser
cpuFiles map[int]group
cpuFilesLock sync.Mutex
numCores int
onlineCPUs []int
eventToCustomEvent map[Event]*CustomEvent
uncore stats.Collector
}
type group struct {
cpuFiles map[string]map[int]readerCloser
names []string
leaderName string
}
var (
isLibpfmInitialized = false
libpmfMutex = sync.Mutex{}
)
const (
groupLeaderFileDescriptor = -1
)
func init() {
libpmfMutex.Lock()
defer libpmfMutex.Unlock()
pErr := C.pfm_initialize()
if pErr != C.PFM_SUCCESS {
fmt.Printf("unable to initialize libpfm: %d", int(pErr))
klog.Errorf("unable to initialize libpfm: %d", int(pErr))
return
}
isLibpfmInitialized = true
}
func newCollector(cgroupPath string, events PerfEvents, numCores int, topology []info.Node) *collector {
collector := &collector{cgroupPath: cgroupPath, events: events, cpuFiles: map[string]map[int]readerCloser{}, numCores: numCores, uncore: NewUncoreCollector(cgroupPath, events, topology)}
func newCollector(cgroupPath string, events PerfEvents, onlineCPUs []int, cpuToSocket map[int]int) *collector {
collector := &collector{cgroupPath: cgroupPath, events: events, onlineCPUs: onlineCPUs, cpuFiles: map[int]group{}, uncore: NewUncoreCollector(cgroupPath, events, cpuToSocket)}
mapEventsToCustomEvents(collector)
return collector
}
@@ -82,48 +92,87 @@ func (c *collector) UpdateStats(stats *info.ContainerStats) error {
stats.PerfStats = []info.PerfStat{}
klog.V(5).Infof("Attempting to update perf_event stats from cgroup %q", c.cgroupPath)
for name, cpus := range c.cpuFiles {
for cpu, file := range cpus {
stat, err := readPerfStat(file, name, cpu)
for _, group := range c.cpuFiles {
for cpu, file := range group.cpuFiles[group.leaderName] {
stat, err := readGroupPerfStat(file, group, cpu, c.cgroupPath)
if err != nil {
klog.Warningf("Unable to read from perf_event_file (event: %q, CPU: %d) for %q: %q", name, cpu, c.cgroupPath, err.Error())
klog.Warningf("Unable to read from perf_event_file (event: %q, CPU: %d) for %q: %q", group.leaderName, cpu, c.cgroupPath, err.Error())
continue
}
klog.V(5).Infof("Read perf event (event: %q, CPU: %d) for %q: %d", name, cpu, c.cgroupPath, stat.Value)
stats.PerfStats = append(stats.PerfStats, *stat)
stats.PerfStats = append(stats.PerfStats, stat...)
}
}
return nil
}
func readPerfStat(file readerCloser, name string, cpu int) (*info.PerfStat, error) {
buf := make([]byte, 32)
_, err := file.Read(buf)
func readGroupPerfStat(file readerCloser, group group, cpu int, cgroupPath string) ([]info.PerfStat, error) {
values, err := getPerfValues(file, group)
if err != nil {
return nil, err
}
perfData := &ReadFormat{}
reader := bytes.NewReader(buf)
perfStats := make([]info.PerfStat, len(values))
for i, value := range values {
klog.V(5).Infof("Read metric for event %q for cpu %d from cgroup %q: %d", value.Name, cpu, cgroupPath, value.Value)
perfStats[i] = info.PerfStat{
PerfValue: value,
Cpu: cpu,
}
}
return perfStats, nil
}
func getPerfValues(file readerCloser, group group) ([]info.PerfValue, error) {
// 24 bytes of GroupReadFormat struct.
// 16 bytes of Values struct for each element in group.
// See https://man7.org/linux/man-pages/man2/perf_event_open.2.html section "Reading results" with PERF_FORMAT_GROUP specified.
buf := make([]byte, 24+16*len(group.names))
_, err := file.Read(buf)
if err != nil {
return []info.PerfValue{}, fmt.Errorf("unable to read perf event group ( leader = %s ): %w", group.leaderName, err)
}
perfData := &GroupReadFormat{}
reader := bytes.NewReader(buf[:24])
err = binary.Read(reader, binary.LittleEndian, perfData)
if err != nil {
return nil, err
return []info.PerfValue{}, fmt.Errorf("unable to decode perf event group ( leader = %s ): %w", group.leaderName, err)
}
values := make([]Values, perfData.Nr)
reader = bytes.NewReader(buf[24:])
err = binary.Read(reader, binary.LittleEndian, values)
if err != nil {
return []info.PerfValue{}, fmt.Errorf("unable to decode perf event group values ( leader = %s ): %w", group.leaderName, err)
}
scalingRatio := 1.0
if perfData.TimeEnabled != 0 {
if perfData.TimeRunning != 0 && perfData.TimeEnabled != 0 {
scalingRatio = float64(perfData.TimeRunning) / float64(perfData.TimeEnabled)
}
stat := info.PerfStat{
Value: uint64(float64(perfData.Value) / scalingRatio),
Name: name,
ScalingRatio: scalingRatio,
Cpu: cpu,
perfValues := make([]info.PerfValue, perfData.Nr)
if scalingRatio != float64(0) {
for i, name := range group.names {
perfValues[i] = info.PerfValue{
ScalingRatio: scalingRatio,
Value: uint64(float64(values[i].Value) / scalingRatio),
Name: name,
}
}
} else {
for i, name := range group.names {
perfValues[i] = info.PerfValue{
ScalingRatio: scalingRatio,
Value: values[i].Value,
Name: name,
}
}
}
return &stat, nil
return perfValues, nil
}
func (c *collector) setup() error {
@@ -136,68 +185,152 @@ func (c *collector) setup() error {
c.cpuFilesLock.Lock()
defer c.cpuFilesLock.Unlock()
cgroupFd := int(cgroup.Fd())
for _, group := range c.events.Core.Events {
customEvent, ok := c.eventToCustomEvent[group[0]]
var err error
if ok {
err = c.setupRawNonGrouped(customEvent, cgroupFd)
} else {
err = c.setupNonGrouped(string(group[0]), cgroupFd)
for i, group := range c.events.Core.Events {
// CPUs file descriptors of group leader needed for perf_event_open.
leaderFileDescriptors := make(map[int]int, len(c.onlineCPUs))
for _, cpu := range c.onlineCPUs {
leaderFileDescriptors[cpu] = groupLeaderFileDescriptor
}
if err != nil {
return err
for j, event := range group.events {
// First element is group leader.
isGroupLeader := j == 0
customEvent, ok := c.eventToCustomEvent[event]
if ok {
config := c.createConfigFromRawEvent(customEvent)
leaderFileDescriptors, err = c.registerEvent(eventInfo{string(customEvent.Name), config, cgroupFd, i, isGroupLeader}, leaderFileDescriptors)
if err != nil {
return err
}
} else {
config, err := c.createConfigFromEvent(event)
if err != nil {
return err
}
leaderFileDescriptors, err = c.registerEvent(eventInfo{string(event), config, cgroupFd, i, isGroupLeader}, leaderFileDescriptors)
if err != nil {
return err
}
// Clean memory allocated by C code.
C.free(unsafe.Pointer(config))
}
}
// Group is prepared so we should reset and enable counting.
for _, fd := range leaderFileDescriptors {
err = unix.IoctlSetInt(fd, unix.PERF_EVENT_IOC_RESET, 0)
if err != nil {
return err
}
err = unix.IoctlSetInt(fd, unix.PERF_EVENT_IOC_ENABLE, 0)
if err != nil {
return err
}
}
}
return nil
}
func (c *collector) setupRawNonGrouped(event *CustomEvent, cgroup int) error {
klog.V(5).Infof("Setting up non-grouped raw perf event %#v", event)
config := createPerfEventAttr(*event)
err := c.registerEvent(config, string(event.Name), cgroup)
func readPerfEventAttr(name string, pfmGetOsEventEncoding func(string, unsafe.Pointer) error) (*unix.PerfEventAttr, error) {
perfEventAttrMemory := C.malloc(C.ulong(unsafe.Sizeof(unix.PerfEventAttr{})))
// Fill memory with 0 values.
C.memset(perfEventAttrMemory, 0, C.ulong(unsafe.Sizeof(unix.PerfEventAttr{})))
err := pfmGetOsEventEncoding(name, unsafe.Pointer(perfEventAttrMemory))
if err != nil {
return err
return nil, err
}
return (*unix.PerfEventAttr)(perfEventAttrMemory), nil
}
func pfmGetOsEventEncoding(name string, perfEventAttrMemory unsafe.Pointer) error {
event := pfmPerfEncodeArgT{}
fstr := C.CString("")
event.fstr = unsafe.Pointer(fstr)
event.attr = perfEventAttrMemory
event.size = C.ulong(unsafe.Sizeof(event))
cSafeName := C.CString(name)
pErr := C.pfm_get_os_event_encoding(cSafeName, C.PFM_PLM0|C.PFM_PLM3, C.PFM_OS_PERF_EVENT, unsafe.Pointer(&event))
if pErr != C.PFM_SUCCESS {
return fmt.Errorf("unable to transform event name %s to perf_event_attr: %d", name, int(pErr))
}
return nil
}
func (c *collector) registerEvent(config *unix.PerfEventAttr, name string, pid int) error {
var cpu int
for cpu = 0; cpu < c.numCores; cpu++ {
groupFd, flags := -1, unix.PERF_FLAG_FD_CLOEXEC|unix.PERF_FLAG_PID_CGROUP
fd, err := unix.PerfEventOpen(config, pid, cpu, groupFd, flags)
type eventInfo struct {
name string
config *unix.PerfEventAttr
pid int
groupIndex int
isGroupLeader bool
}
func (c *collector) registerEvent(event eventInfo, leaderFileDescriptors map[int]int) (map[int]int, error) {
newLeaderFileDescriptors := make(map[int]int, len(c.onlineCPUs))
var pid, flags int
if event.isGroupLeader {
pid = event.pid
flags = unix.PERF_FLAG_FD_CLOEXEC | unix.PERF_FLAG_PID_CGROUP
} else {
pid = -1
flags = unix.PERF_FLAG_FD_CLOEXEC
}
setAttributes(event.config, event.isGroupLeader)
for _, cpu := range c.onlineCPUs {
fd, err := unix.PerfEventOpen(event.config, pid, cpu, leaderFileDescriptors[cpu], flags)
if err != nil {
return fmt.Errorf("setting up perf event %#v failed: %q", config, err)
return nil, fmt.Errorf("setting up perf event %#v failed: %q", event.config, err)
}
perfFile := os.NewFile(uintptr(fd), name)
perfFile := os.NewFile(uintptr(fd), event.name)
if perfFile == nil {
return fmt.Errorf("unable to create os.File from file descriptor %#v", fd)
return nil, fmt.Errorf("unable to create os.File from file descriptor %#v", fd)
}
c.addEventFile(name, cpu, perfFile)
c.addEventFile(event.groupIndex, event.name, cpu, perfFile)
// If group leader, save fd for others.
if event.isGroupLeader {
newLeaderFileDescriptors[cpu] = fd
}
}
return nil
if event.isGroupLeader {
return newLeaderFileDescriptors, nil
}
return leaderFileDescriptors, nil
}
func (c *collector) addEventFile(name string, cpu int, perfFile *os.File) {
_, ok := c.cpuFiles[name]
func (c *collector) addEventFile(index int, name string, cpu int, perfFile *os.File) {
_, ok := c.cpuFiles[index]
if !ok {
c.cpuFiles[name] = map[int]readerCloser{}
c.cpuFiles[index] = group{
leaderName: name,
cpuFiles: map[string]map[int]readerCloser{},
}
}
c.cpuFiles[name][cpu] = perfFile
}
func (c *collector) setupNonGrouped(name string, cgroup int) error {
perfEventAttr, err := getPerfEventAttr(name)
if err != nil {
return err
_, ok = c.cpuFiles[index].cpuFiles[name]
if !ok {
c.cpuFiles[index].cpuFiles[name] = map[int]readerCloser{}
}
defer C.free(unsafe.Pointer(perfEventAttr))
return c.registerEvent(perfEventAttr, name, cgroup)
c.cpuFiles[index].cpuFiles[name][cpu] = perfFile
// Check if name is already stored.
for _, have := range c.cpuFiles[index].names {
if name == have {
return
}
}
// Otherwise save it.
c.cpuFiles[index] = group{
cpuFiles: c.cpuFiles[index].cpuFiles,
names: append(c.cpuFiles[index].names, name),
leaderName: c.cpuFiles[index].leaderName,
}
}
func createPerfEventAttr(event CustomEvent) *unix.PerfEventAttr {
@@ -214,43 +347,20 @@ func createPerfEventAttr(event CustomEvent) *unix.PerfEventAttr {
config.Ext2 = event.Config[2]
}
setAttributes(config)
klog.V(5).Infof("perf_event_attr struct prepared: %#v", config)
return config
}
func getPerfEventAttr(name string) (*unix.PerfEventAttr, error) {
if !isLibpfmInitialized {
return nil, fmt.Errorf("libpfm4 is not initialized, cannot proceed with setting perf events up")
func setAttributes(config *unix.PerfEventAttr, leader bool) {
config.Sample_type = unix.PERF_SAMPLE_IDENTIFIER
config.Read_format = unix.PERF_FORMAT_TOTAL_TIME_ENABLED | unix.PERF_FORMAT_TOTAL_TIME_RUNNING | unix.PERF_FORMAT_GROUP | unix.PERF_FORMAT_ID
config.Bits = unix.PerfBitInherit
// Group leader should have this flag set to disable counting until all group would be prepared.
if leader {
config.Bits |= unix.PerfBitDisabled
}
perfEventAttrMemory := C.malloc(C.ulong(unsafe.Sizeof(unix.PerfEventAttr{})))
event := pfmPerfEncodeArgT{}
perfEventAttr := (*unix.PerfEventAttr)(perfEventAttrMemory)
fstr := C.CString("")
event.fstr = unsafe.Pointer(fstr)
event.attr = perfEventAttrMemory
event.size = C.ulong(unsafe.Sizeof(event))
cSafeName := C.CString(name)
pErr := C.pfm_get_os_event_encoding(cSafeName, C.PFM_PLM0|C.PFM_PLM3, C.PFM_OS_PERF_EVENT, unsafe.Pointer(&event))
if pErr != C.PFM_SUCCESS {
return nil, fmt.Errorf("unable to transform event name %s to perf_event_attr: %v", name, int(pErr))
}
klog.V(5).Infof("perf_event_attr: %#v", perfEventAttr)
setAttributes(perfEventAttr)
return perfEventAttr, nil
}
func setAttributes(config *unix.PerfEventAttr) {
config.Sample_type = perfSampleIdentifier
config.Read_format = unix.PERF_FORMAT_TOTAL_TIME_ENABLED | unix.PERF_FORMAT_TOTAL_TIME_RUNNING | unix.PERF_FORMAT_ID
config.Bits = perfAttrBitsInherit | perfAttrBitsExcludeGuest
config.Size = uint32(unsafe.Sizeof(unix.PerfEventAttr{}))
}
@@ -259,15 +369,17 @@ func (c *collector) Destroy() {
c.cpuFilesLock.Lock()
defer c.cpuFilesLock.Unlock()
for name, files := range c.cpuFiles {
for cpu, file := range files {
klog.V(5).Infof("Closing perf_event file descriptor for cgroup %q, event %q and CPU %d", c.cgroupPath, name, cpu)
err := file.Close()
if err != nil {
klog.Warningf("Unable to close perf_event file descriptor for cgroup %q, event %q and CPU %d", c.cgroupPath, name, cpu)
for _, group := range c.cpuFiles {
for name, files := range group.cpuFiles {
for cpu, file := range files {
klog.V(5).Infof("Closing perf_event file descriptor for cgroup %q, event %q and CPU %d", c.cgroupPath, name, cpu)
err := file.Close()
if err != nil {
klog.Warningf("Unable to close perf_event file descriptor for cgroup %q, event %q and CPU %d", c.cgroupPath, name, cpu)
}
}
delete(group.cpuFiles, name)
}
delete(c.cpuFiles, name)
}
}
@@ -292,3 +404,27 @@ func mapEventsToCustomEvents(collector *collector) {
collector.eventToCustomEvent[event.Name] = &collector.events.Core.CustomEvents[key]
}
}
func (c *collector) createConfigFromRawEvent(event *CustomEvent) *unix.PerfEventAttr {
klog.V(5).Infof("Setting up raw perf event %#v", event)
config := createPerfEventAttr(*event)
klog.V(5).Infof("perf_event_attr: %#v", config)
return config
}
func (c *collector) createConfigFromEvent(event Event) (*unix.PerfEventAttr, error) {
klog.V(5).Infof("Setting up perf event %s", string(event))
config, err := readPerfEventAttr(string(event), pfmGetOsEventEncoding)
if err != nil {
C.free((unsafe.Pointer)(config))
return nil, err
}
klog.V(5).Infof("perf_event_attr: %#v", config)
return config, nil
}

View File

@@ -34,7 +34,7 @@ type PerfEvents struct {
type Events struct {
// List of perf events' names to be measured.
Events [][]Event `json:"events"`
Events []Group `json:"events"`
// List of custom perf events' to be measured. It is impossible to
// specify some events using their names and in such case you have
@@ -89,3 +89,39 @@ func parseConfig(file *os.File) (events PerfEvents, err error) {
}
return
}
type Group struct {
events []Event
array bool
}
func (g *Group) UnmarshalJSON(b []byte) error {
var jsonObj interface{}
err := json.Unmarshal(b, &jsonObj)
if err != nil {
return err
}
switch obj := jsonObj.(type) {
case string:
*g = Group{
events: []Event{Event(obj)},
array: false,
}
return nil
case []interface{}:
group := Group{
events: make([]Event, 0, len(obj)),
array: true,
}
for _, v := range obj {
value, ok := v.(string)
if !ok {
return fmt.Errorf("cannot unmarshal %v", value)
}
group.events = append(group.events, Event(value))
}
*g = group
return nil
}
return fmt.Errorf("unsupported type")
}

View File

@@ -23,48 +23,44 @@ import (
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/stats"
"github.com/google/cadvisor/utils/sysinfo"
)
type manager struct {
events PerfEvents
numCores int
topology []info.Node
events PerfEvents
onlineCPUs []int
cpuToSocket map[int]int
stats.NoopDestroy
}
func NewManager(configFile string, numCores int, topology []info.Node) (stats.Manager, error) {
func NewManager(configFile string, topology []info.Node) (stats.Manager, error) {
if configFile == "" {
return &stats.NoopManager{}, nil
}
file, err := os.Open(configFile)
if err != nil {
return nil, fmt.Errorf("Unable to read configuration file %q: %q", configFile, err)
return nil, fmt.Errorf("unable to read configuration file %q: %w", configFile, err)
}
config, err := parseConfig(file)
if err != nil {
return nil, fmt.Errorf("Unable to read configuration file %q: %q", configFile, err)
return nil, fmt.Errorf("unable to parse configuration file %q: %w", configFile, err)
}
if areGroupedEventsUsed(config) {
return nil, fmt.Errorf("event grouping is not supported you must modify config file at %s", configFile)
onlineCPUs := sysinfo.GetOnlineCPUs(topology)
cpuToSocket := make(map[int]int)
for _, cpu := range onlineCPUs {
cpuToSocket[cpu] = sysinfo.GetSocketFromCPU(topology, cpu)
}
return &manager{events: config, numCores: numCores, topology: topology}, nil
}
func areGroupedEventsUsed(events PerfEvents) bool {
for _, group := range events.Core.Events {
if len(group) > 1 {
return true
}
}
return false
return &manager{events: config, onlineCPUs: onlineCPUs, cpuToSocket: cpuToSocket}, nil
}
func (m *manager) GetCollector(cgroupPath string) (stats.Collector, error) {
collector := newCollector(cgroupPath, m.events, m.numCores, m.topology)
collector := newCollector(cgroupPath, m.events, m.onlineCPUs, m.cpuToSocket)
err := collector.setup()
if err != nil {
collector.Destroy()

View File

@@ -24,7 +24,7 @@ import (
"k8s.io/klog/v2"
)
func NewManager(configFile string, numCores int, topology []info.Node) (stats.Manager, error) {
func NewManager(configFile string, topology []info.Node) (stats.Manager, error) {
klog.V(1).Info("cAdvisor is build without cgo and/or libpfm support. Perf event counters are not available.")
return &stats.NoopManager{}, nil
}

View File

@@ -23,18 +23,17 @@ import (
"unsafe"
)
const (
perfSampleIdentifier = 1 << 16
perfAttrBitsInherit = 1 << 1
perfAttrBitsExcludeGuest = 1 << 20
)
// ReadFormat allows to read perf event's value for non-grouped events
type ReadFormat struct {
Value uint64 /* The value of the event */
// GroupReadFormat allows to read perf event's values for grouped events.
// See https://man7.org/linux/man-pages/man2/perf_event_open.2.html section "Reading results" with PERF_FORMAT_GROUP specified.
type GroupReadFormat struct {
Nr uint64 /* The number of events */
TimeEnabled uint64 /* if PERF_FORMAT_TOTAL_TIME_ENABLED */
TimeRunning uint64 /* if PERF_FORMAT_TOTAL_TIME_RUNNING */
ID uint64 /* if PERF_FORMAT_ID */
}
type Values struct {
Value uint64 /* The value of the event */
ID uint64 /* if PERF_FORMAT_ID */
}
// pfmPerfEncodeArgT represents structure that is used to parse perf event nam

View File

@@ -23,12 +23,11 @@ package perf
// #include <stdlib.h>
import "C"
import (
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
@@ -40,7 +39,6 @@ import (
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/stats"
"github.com/google/cadvisor/utils/sysinfo"
)
type pmu struct {
@@ -55,9 +53,10 @@ const (
pmuCpumaskFilename = "cpumask"
systemDevicesPath = "/sys/devices"
rootPerfEventPath = "/sys/fs/cgroup/perf_event"
uncorePID = -1
)
func getPMU(pmus []pmu, gotType uint32) (*pmu, error) {
func getPMU(pmus uncorePMUs, gotType uint32) (*pmu, error) {
for _, pmu := range pmus {
if pmu.typeOf == gotType {
return &pmu, nil
@@ -98,7 +97,7 @@ func readUncorePMU(path string, name string, cpumaskRegexp *regexp.Regexp) (*pmu
}
func getUncorePMUs(devicesPath string) (uncorePMUs, error) {
pmus := make(uncorePMUs, 0)
pmus := make(uncorePMUs)
// Depends on platform, cpu mask could be for example in form "0-1" or "0,1".
cpumaskRegexp := regexp.MustCompile("[-,\n]")
@@ -126,32 +125,33 @@ func getUncorePMUs(devicesPath string) (uncorePMUs, error) {
}
type uncoreCollector struct {
cpuFiles map[string]map[string]map[int]readerCloser
cpuFilesLock sync.Mutex
events [][]Event
cpuFiles map[int]map[string]group
events []Group
eventToCustomEvent map[Event]*CustomEvent
topology []info.Node
cpuToSocket map[int]int
// Handle for mocking purposes.
perfEventOpen func(attr *unix.PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error)
ioctlSetInt func(fd int, req uint, value int) error
}
func NewUncoreCollector(cgroupPath string, events PerfEvents, topology []info.Node) stats.Collector {
func NewUncoreCollector(cgroupPath string, events PerfEvents, cpuToSocket map[int]int) stats.Collector {
if cgroupPath != rootPerfEventPath {
// Uncore metric doesn't exists for cgroups, only for entire platform.
return &stats.NoopCollector{}
}
collector := &uncoreCollector{topology: topology}
// Default implementation of Linux perf_event_open function.
collector.perfEventOpen = unix.PerfEventOpen
collector := &uncoreCollector{
cpuToSocket: cpuToSocket,
perfEventOpen: unix.PerfEventOpen,
ioctlSetInt: unix.IoctlSetInt,
}
err := collector.setup(events, systemDevicesPath)
if err != nil {
formatedError := fmt.Errorf("unable to setup uncore perf event collector: %v", err)
klog.V(5).Infof("Perf uncore metrics will not be available: %s", formatedError)
klog.Errorf("Perf uncore metrics will not be available: unable to setup uncore perf event collector: %v", err)
return &stats.NoopCollector{}
}
@@ -159,49 +159,100 @@ func NewUncoreCollector(cgroupPath string, events PerfEvents, topology []info.No
}
func (c *uncoreCollector) setup(events PerfEvents, devicesPath string) error {
var err error
readUncorePMUs, err := getUncorePMUs(devicesPath)
if err != nil {
return err
}
// Maping from event name, pmu type, cpu.
c.cpuFiles = make(map[string]map[string]map[int]readerCloser)
c.cpuFiles = make(map[int]map[string]group)
c.events = events.Uncore.Events
c.eventToCustomEvent = parseUncoreEvents(events.Uncore)
c.cpuFilesLock.Lock()
defer c.cpuFilesLock.Unlock()
for _, group := range c.events {
if len(group) > 1 {
klog.Warning("grouping uncore perf events is not supported!")
continue
}
eventName, pmuPrefix := parseEventName(string(group[0]))
var err error
customEvent, ok := c.eventToCustomEvent[group[0]]
if ok {
if customEvent.Type != 0 {
pmus := obtainPMUs("uncore", readUncorePMUs)
err = c.setupRawNonGroupedUncore(customEvent, pmus)
} else {
pmus := obtainPMUs(pmuPrefix, readUncorePMUs)
err = c.setupRawNonGroupedUncore(customEvent, pmus)
}
} else {
pmus := obtainPMUs(pmuPrefix, readUncorePMUs)
err = c.setupNonGroupedUncore(eventName, pmus)
}
for i, group := range c.events {
// Check what PMUs are needed.
groupPMUs, err := parsePMUs(group, readUncorePMUs, c.eventToCustomEvent)
if err != nil {
return err
}
err = checkGroup(group, groupPMUs)
if err != nil {
return err
}
// CPUs file descriptors of group leader needed for perf_event_open.
leaderFileDescriptors := make(map[string]map[uint32]int)
for _, pmu := range readUncorePMUs {
leaderFileDescriptors[pmu.name] = make(map[uint32]int)
for _, cpu := range pmu.cpus {
leaderFileDescriptors[pmu.name][cpu] = groupLeaderFileDescriptor
}
}
for _, event := range group.events {
eventName, _ := parseEventName(string(event))
customEvent, ok := c.eventToCustomEvent[event]
if ok {
err = c.setupRawEvent(customEvent, groupPMUs[event], i, leaderFileDescriptors)
} else {
err = c.setupEvent(eventName, groupPMUs[event], i, leaderFileDescriptors)
}
if err != nil {
return err
}
}
// Group is prepared so we should reset and enable counting.
for _, pmuCPUs := range leaderFileDescriptors {
for _, fd := range pmuCPUs {
// Call only for used PMUs.
if fd != groupLeaderFileDescriptor {
err = c.ioctlSetInt(fd, unix.PERF_EVENT_IOC_RESET, 0)
if err != nil {
return err
}
err = c.ioctlSetInt(fd, unix.PERF_EVENT_IOC_ENABLE, 0)
if err != nil {
return err
}
}
}
}
}
return nil
}
func checkGroup(group Group, eventPMUs map[Event]uncorePMUs) error {
if group.array {
var pmu uncorePMUs
for _, event := range group.events {
if len(eventPMUs[event]) > 1 {
return fmt.Errorf("the events in group usually have to be from single PMU, try reorganizing the \"%v\" group", group.events)
}
if len(eventPMUs[event]) == 1 {
if pmu == nil {
pmu = eventPMUs[event]
continue
}
eq := reflect.DeepEqual(pmu, eventPMUs[event])
if !eq {
return fmt.Errorf("the events in group usually have to be from the same PMU, try reorganizing the \"%v\" group", group.events)
}
}
}
return nil
}
if len(eventPMUs[group.events[0]]) < 1 {
return fmt.Errorf("the event %q don't have any PMU to count with", group.events[0])
}
return nil
}
func parseEventName(eventName string) (string, string) {
// First "/" separate pmu prefix and event name
// ex. "uncore_imc_0/cas_count_read" -> uncore_imc_0 and cas_count_read.
@@ -214,14 +265,35 @@ func parseEventName(eventName string) (string, string) {
return eventName, pmuPrefix
}
func obtainPMUs(want string, gotPMUs uncorePMUs) []pmu {
var pmus []pmu
func parsePMUs(group Group, pmus uncorePMUs, customEvents map[Event]*CustomEvent) (map[Event]uncorePMUs, error) {
eventPMUs := make(map[Event]uncorePMUs)
for _, event := range group.events {
_, prefix := parseEventName(string(event))
custom, ok := customEvents[event]
if ok {
if custom.Type != 0 {
pmu, err := getPMU(pmus, custom.Type)
if err != nil {
return nil, err
}
eventPMUs[event] = uncorePMUs{pmu.name: *pmu}
continue
}
}
eventPMUs[event] = obtainPMUs(prefix, pmus)
}
return eventPMUs, nil
}
func obtainPMUs(want string, gotPMUs uncorePMUs) uncorePMUs {
pmus := make(uncorePMUs)
if want == "" {
return pmus
}
for _, pmu := range gotPMUs {
if strings.HasPrefix(pmu.name, want) {
pmus = append(pmus, pmu)
pmus[pmu.name] = pmu
}
}
@@ -230,11 +302,13 @@ func obtainPMUs(want string, gotPMUs uncorePMUs) []pmu {
func parseUncoreEvents(events Events) map[Event]*CustomEvent {
eventToCustomEvent := map[Event]*CustomEvent{}
for _, uncoreEvent := range events.Events {
for _, customEvent := range events.CustomEvents {
if uncoreEvent[0] == customEvent.Name {
eventToCustomEvent[customEvent.Name] = &customEvent
break
for _, group := range events.Events {
for _, uncoreEvent := range group.events {
for _, customEvent := range events.CustomEvents {
if uncoreEvent == customEvent.Name {
eventToCustomEvent[customEvent.Name] = &customEvent
break
}
}
}
}
@@ -246,34 +320,37 @@ func (c *uncoreCollector) Destroy() {
c.cpuFilesLock.Lock()
defer c.cpuFilesLock.Unlock()
for name, pmus := range c.cpuFiles {
for pmu, cpus := range pmus {
for cpu, file := range cpus {
klog.V(5).Infof("Closing uncore perf_event file descriptor for event %q, PMU %s and CPU %d", name, pmu, cpu)
err := file.Close()
if err != nil {
klog.Warningf("Unable to close perf_event file descriptor for event %q, PMU %s and CPU %d", name, pmu, cpu)
for groupIndex, groupPMUs := range c.cpuFiles {
for pmu, group := range groupPMUs {
for name, cpus := range group.cpuFiles {
for cpu, file := range cpus {
klog.V(5).Infof("Closing uncore perf_event file descriptor for event %q, PMU %s and CPU %d", name, pmu, cpu)
err := file.Close()
if err != nil {
klog.Warningf("Unable to close perf_event file descriptor for event %q, PMU %s and CPU %d", name, pmu, cpu)
}
}
delete(group.cpuFiles, name)
}
delete(pmus, pmu)
delete(groupPMUs, pmu)
}
delete(c.cpuFiles, name)
delete(c.cpuFiles, groupIndex)
}
}
func (c *uncoreCollector) UpdateStats(stats *info.ContainerStats) error {
klog.V(5).Info("Attempting to update uncore perf_event stats")
for name, pmus := range c.cpuFiles {
for pmu, cpus := range pmus {
for cpu, file := range cpus {
stat, err := readPerfUncoreStat(file, name, cpu, pmu, c.topology)
for _, groupPMUs := range c.cpuFiles {
for pmu, group := range groupPMUs {
for cpu, file := range group.cpuFiles[group.leaderName] {
stat, err := readPerfUncoreStat(file, group, cpu, pmu, c.cpuToSocket)
if err != nil {
return fmt.Errorf("unable to read from uncore perf_event_file (event: %q, CPU: %d, PMU: %s): %q", name, cpu, pmu, err.Error())
klog.Warningf("Unable to read from perf_event_file (event: %q, CPU: %d) for %q: %q", group.leaderName, cpu, pmu, err.Error())
continue
}
klog.V(5).Infof("Read uncore perf event (event: %q, CPU: %d, PMU: %s): %d", name, cpu, pmu, stat.Value)
stats.PerfUncoreStats = append(stats.PerfUncoreStats, *stat)
stats.PerfUncoreStats = append(stats.PerfUncoreStats, stat...)
}
}
}
@@ -281,112 +358,144 @@ func (c *uncoreCollector) UpdateStats(stats *info.ContainerStats) error {
return nil
}
func (c *uncoreCollector) setupRawNonGroupedUncore(event *CustomEvent, pmus []pmu) error {
klog.V(5).Infof("Setting up non-grouped raw perf uncore event %#v", event)
if event.Type == 0 {
// PMU isn't set. Register event for all PMUs.
for _, pmu := range pmus {
newEvent := CustomEvent{
Type: pmu.typeOf,
Config: event.Config,
Name: event.Name,
}
config := createPerfEventAttr(newEvent)
err := c.registerUncoreEvent(config, string(newEvent.Name), pmu.cpus, pmu.name)
if err != nil {
return err
}
}
return nil
} else {
// Register event for the PMU.
config := createPerfEventAttr(*event)
pmu, err := getPMU(pmus, event.Type)
if err != nil {
return err
}
return c.registerUncoreEvent(config, string(event.Name), pmu.cpus, pmu.name)
func (c *uncoreCollector) setupEvent(name string, pmus uncorePMUs, groupIndex int, leaderFileDescriptors map[string]map[uint32]int) error {
if !isLibpfmInitialized {
return fmt.Errorf("libpfm4 is not initialized, cannot proceed with setting perf events up")
}
}
func (c *uncoreCollector) setupNonGroupedUncore(name string, pmus []pmu) error {
perfEventAttr, err := getPerfEventAttr(name)
klog.V(5).Infof("Setting up uncore perf event %s", name)
config, err := readPerfEventAttr(name, pfmGetOsEventEncoding)
if err != nil {
C.free((unsafe.Pointer)(config))
return err
}
defer C.free(unsafe.Pointer(perfEventAttr))
klog.V(5).Infof("Setting up non-grouped uncore perf event %s", name)
// Register event for all memory controllers.
for _, pmu := range pmus {
perfEventAttr.Type = pmu.typeOf
err = c.registerUncoreEvent(perfEventAttr, name, pmu.cpus, pmu.name)
config.Type = pmu.typeOf
isGroupLeader := leaderFileDescriptors[pmu.name][pmu.cpus[0]] == groupLeaderFileDescriptor
setAttributes(config, isGroupLeader)
leaderFileDescriptors[pmu.name], err = c.registerEvent(eventInfo{name, config, uncorePID, groupIndex, isGroupLeader}, pmu, leaderFileDescriptors[pmu.name])
if err != nil {
return err
}
}
// Clean memory allocated by C code.
C.free(unsafe.Pointer(config))
return nil
}
func (c *uncoreCollector) registerUncoreEvent(config *unix.PerfEventAttr, name string, cpus []uint32, pmu string) error {
for _, cpu := range cpus {
groupFd, pid, flags := -1, -1, 0
fd, err := c.perfEventOpen(config, pid, int(cpu), groupFd, flags)
func (c *uncoreCollector) registerEvent(eventInfo eventInfo, pmu pmu, leaderFileDescriptors map[uint32]int) (map[uint32]int, error) {
newLeaderFileDescriptors := make(map[uint32]int)
isGroupLeader := false
for _, cpu := range pmu.cpus {
groupFd, flags := leaderFileDescriptors[cpu], 0
fd, err := c.perfEventOpen(eventInfo.config, eventInfo.pid, int(cpu), groupFd, flags)
if err != nil {
return fmt.Errorf("setting up perf event %#v failed: %q", config, err)
return nil, fmt.Errorf("setting up perf event %#v failed: %q | (pmu: %q, groupFd: %d, cpu: %d)", eventInfo.config, err, pmu, groupFd, cpu)
}
perfFile := os.NewFile(uintptr(fd), name)
perfFile := os.NewFile(uintptr(fd), eventInfo.name)
if perfFile == nil {
return fmt.Errorf("unable to create os.File from file descriptor %#v", fd)
return nil, fmt.Errorf("unable to create os.File from file descriptor %#v", fd)
}
c.addEventFile(name, pmu, int(cpu), perfFile)
c.addEventFile(eventInfo.groupIndex, eventInfo.name, pmu.name, int(cpu), perfFile)
// If group leader, save fd for others.
if leaderFileDescriptors[cpu] == groupLeaderFileDescriptor {
newLeaderFileDescriptors[cpu] = fd
isGroupLeader = true
}
}
if isGroupLeader {
return newLeaderFileDescriptors, nil
}
return leaderFileDescriptors, nil
}
func (c *uncoreCollector) addEventFile(index int, name string, pmu string, cpu int, perfFile *os.File) {
_, ok := c.cpuFiles[index]
if !ok {
c.cpuFiles[index] = map[string]group{}
}
_, ok = c.cpuFiles[index][pmu]
if !ok {
c.cpuFiles[index][pmu] = group{
cpuFiles: map[string]map[int]readerCloser{},
leaderName: name,
}
}
_, ok = c.cpuFiles[index][pmu].cpuFiles[name]
if !ok {
c.cpuFiles[index][pmu].cpuFiles[name] = map[int]readerCloser{}
}
c.cpuFiles[index][pmu].cpuFiles[name][cpu] = perfFile
// Check if name is already stored.
for _, have := range c.cpuFiles[index][pmu].names {
if name == have {
return
}
}
// Otherwise save it.
c.cpuFiles[index][pmu] = group{
cpuFiles: c.cpuFiles[index][pmu].cpuFiles,
names: append(c.cpuFiles[index][pmu].names, name),
leaderName: c.cpuFiles[index][pmu].leaderName,
}
}
func (c *uncoreCollector) setupRawEvent(event *CustomEvent, pmus uncorePMUs, groupIndex int, leaderFileDescriptors map[string]map[uint32]int) error {
klog.V(5).Infof("Setting up raw perf uncore event %#v", event)
for _, pmu := range pmus {
newEvent := CustomEvent{
Type: pmu.typeOf,
Config: event.Config,
Name: event.Name,
}
config := createPerfEventAttr(newEvent)
isGroupLeader := leaderFileDescriptors[pmu.name][pmu.cpus[0]] == groupLeaderFileDescriptor
setAttributes(config, isGroupLeader)
var err error
leaderFileDescriptors[pmu.name], err = c.registerEvent(eventInfo{string(newEvent.Name), config, uncorePID, groupIndex, isGroupLeader}, pmu, leaderFileDescriptors[pmu.name])
if err != nil {
return err
}
}
return nil
}
func (c *uncoreCollector) addEventFile(name string, pmu string, cpu int, perfFile *os.File) {
_, ok := c.cpuFiles[name]
if !ok {
c.cpuFiles[name] = map[string]map[int]readerCloser{}
}
_, ok = c.cpuFiles[name][pmu]
if !ok {
c.cpuFiles[name][pmu] = map[int]readerCloser{}
}
c.cpuFiles[name][pmu][cpu] = perfFile
}
func readPerfUncoreStat(file readerCloser, name string, cpu int, pmu string, topology []info.Node) (*info.PerfUncoreStat, error) {
buf := make([]byte, 32)
_, err := file.Read(buf)
if err != nil {
return nil, err
}
perfData := &ReadFormat{}
reader := bytes.NewReader(buf)
err = binary.Read(reader, binary.LittleEndian, perfData)
func readPerfUncoreStat(file readerCloser, group group, cpu int, pmu string, cpuToSocket map[int]int) ([]info.PerfUncoreStat, error) {
values, err := getPerfValues(file, group)
if err != nil {
return nil, err
}
scalingRatio := 1.0
if perfData.TimeEnabled != 0 {
scalingRatio = float64(perfData.TimeRunning) / float64(perfData.TimeEnabled)
socket, ok := cpuToSocket[cpu]
if !ok {
// Socket is unknown.
socket = -1
}
stat := info.PerfUncoreStat{
Value: uint64(float64(perfData.Value) / scalingRatio),
Name: name,
ScalingRatio: scalingRatio,
Socket: sysinfo.GetSocketFromCPU(topology, cpu),
PMU: pmu,
perfUncoreStats := make([]info.PerfUncoreStat, len(values))
for i, value := range values {
klog.V(5).Infof("Read metric for event %q for cpu %d from pmu %q: %d", value.Name, cpu, pmu, value.Value)
perfUncoreStats[i] = info.PerfUncoreStat{
PerfValue: value,
Socket: socket,
PMU: pmu,
}
}
return &stat, nil
return perfUncoreStats, nil
}

View File

@@ -377,7 +377,7 @@ func getNodeMemInfo(sysFs sysfs.SysFs, nodeDir string) (uint64, error) {
return uint64(memory), nil
}
// getCoresInfo retruns infromation about physical cores
// getCoresInfo returns information about physical cores
func getCoresInfo(sysFs sysfs.SysFs, cpuDirs []string) ([]info.Core, error) {
cores := make([]info.Core, 0, len(cpuDirs))
for _, cpuDir := range cpuDirs {
@@ -523,3 +523,14 @@ func GetSocketFromCPU(topology []info.Node, cpu int) int {
}
return -1
}
// GetOnlineCPUs returns available cores.
func GetOnlineCPUs(topology []info.Node) []int {
onlineCPUs := make([]int, 0)
for _, node := range topology {
for _, core := range node.Cores {
onlineCPUs = append(onlineCPUs, core.Threads...)
}
}
return onlineCPUs
}