libvirt/src/util/virperf.c

397 lines
10 KiB
C
Raw Normal View History

/*
* virperf.c: methods for managing perf events
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <unistd.h>
#ifndef WIN32
# include <sys/ioctl.h>
#endif
#if defined WITH_SYS_SYSCALL_H
# include <sys/syscall.h>
#endif
#include "virperf.h"
#include "virerror.h"
#include "virlog.h"
#include "virfile.h"
#include "virstring.h"
#include "virtypedparam.h"
#include "viralloc.h"
VIR_LOG_INIT("util.perf");
#define VIR_FROM_THIS VIR_FROM_PERF
VIR_ENUM_IMPL(virPerfEvent,
VIR_PERF_EVENT_LAST,
"cmt", "mbmt", "mbml",
"cpu_cycles", "instructions",
"cache_references", "cache_misses",
"branch_instructions", "branch_misses",
"bus_cycles", "stalled_cycles_frontend",
"stalled_cycles_backend", "ref_cpu_cycles",
"cpu_clock", "task_clock", "page_faults",
"context_switches", "cpu_migrations",
"page_faults_min", "page_faults_maj",
"alignment_faults", "emulation_faults",
);
struct virPerfEvent {
int fd;
bool enabled;
union {
/* cmt */
struct {
int scale;
} cmt;
} efields;
};
struct _virPerf {
struct virPerfEvent events[VIR_PERF_EVENT_LAST];
};
#if defined(__linux__) && defined(WITH_SYS_SYSCALL_H)
# include <linux/perf_event.h>
struct virPerfEventAttr {
unsigned int attrType;
unsigned long long attrConfig;
};
static struct virPerfEventAttr attrs[] = {
[VIR_PERF_EVENT_CMT] = {
.attrType = 0,
.attrConfig = 1
},
[VIR_PERF_EVENT_MBMT] = {
.attrType = 0,
.attrConfig = 2
},
[VIR_PERF_EVENT_MBML] = {
.attrType = 0,
.attrConfig = 3
},
[VIR_PERF_EVENT_CPU_CYCLES] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_CPU_CYCLES
},
[VIR_PERF_EVENT_INSTRUCTIONS] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_INSTRUCTIONS
},
[VIR_PERF_EVENT_CACHE_REFERENCES] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_CACHE_REFERENCES
},
[VIR_PERF_EVENT_CACHE_MISSES] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_CACHE_MISSES
},
[VIR_PERF_EVENT_BRANCH_INSTRUCTIONS] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_BRANCH_INSTRUCTIONS
},
[VIR_PERF_EVENT_BRANCH_MISSES] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_BRANCH_MISSES
},
[VIR_PERF_EVENT_BUS_CYCLES] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_BUS_CYCLES
},
[VIR_PERF_EVENT_STALLED_CYCLES_FRONTEND] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
},
[VIR_PERF_EVENT_STALLED_CYCLES_BACKEND] = {
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_STALLED_CYCLES_BACKEND
},
[VIR_PERF_EVENT_REF_CPU_CYCLES] = {
# ifdef PERF_COUNT_HW_REF_CPU_CYCLES
.attrType = PERF_TYPE_HARDWARE,
.attrConfig = PERF_COUNT_HW_REF_CPU_CYCLES
# else
.attrType = 0,
.attrConfig = 0,
# endif
},
[VIR_PERF_EVENT_CPU_CLOCK] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_CPU_CLOCK
},
[VIR_PERF_EVENT_TASK_CLOCK] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_TASK_CLOCK
},
[VIR_PERF_EVENT_PAGE_FAULTS] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_PAGE_FAULTS
},
[VIR_PERF_EVENT_CONTEXT_SWITCHES] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_CONTEXT_SWITCHES
},
[VIR_PERF_EVENT_CPU_MIGRATIONS] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_CPU_MIGRATIONS
},
[VIR_PERF_EVENT_PAGE_FAULTS_MIN] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_PAGE_FAULTS_MIN
},
[VIR_PERF_EVENT_PAGE_FAULTS_MAJ] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_PAGE_FAULTS_MAJ
},
[VIR_PERF_EVENT_ALIGNMENT_FAULTS] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_ALIGNMENT_FAULTS
},
[VIR_PERF_EVENT_EMULATION_FAULTS] = {
.attrType = PERF_TYPE_SOFTWARE,
.attrConfig = PERF_COUNT_SW_EMULATION_FAULTS
},
};
G_STATIC_ASSERT(G_N_ELEMENTS(attrs) == VIR_PERF_EVENT_LAST);
virPerfEventIsEnabled: Don't crash on shut off domains So imagine the following. You connect read only to a daemon and try to fetch stats for a shut off domain, e.g.: virsh -r domstats $dom but all of a sudden, virsh instead of printing the stats throws the following error at you: error: Disconnected from qemu:///system due to I/O error error: End of file while reading data: Input/output error The daemon crashed. This is its backtrace: #0 0x00007fa43e3751a8 in virPerfEventIsEnabled (perf=0x0, type=VIR_PERF_EVENT_MBMT) at util/virperf.c:241 #1 0x00007fa424a9f042 in qemuDomainGetStatsPerf (driver=0x7fa3f4022a30, dom=0x7fa3f40e24c0, record=0x7fa41c000e20, maxparams=0x7fa4360b38d0, privflags=1) at qemu/qemu_driver.c:19110 #2 0x00007fa424a9f2e7 in qemuDomainGetStats (conn=0x7fa41c001b20, dom=0x7fa3f40e24c0, stats=127, record=0x7fa4360b3970, flags=1) at qemu/qemu_driver.c:19213 #3 0x00007fa424a9f672 in qemuConnectGetAllDomainStats (conn=0x7fa41c001b20, doms=0x7fa41c0017f0, ndoms=1, stats=127, retStats=0x7fa4360b3a50, flags=0) at qemu/qemu_driver.c:19303 #4 0x00007fa43e4e15f6 in virDomainListGetStats (doms=0x7fa41c0017f0, stats=0, retStats=0x7fa4360b3a50, flags=0) at libvirt-domain.c:11615 Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7f28d1a38700 (LWP 16154)] 0x00007f28da4fa1a8 in virPerfEventIsEnabled (perf=0x0, type=VIR_PERF_EVENT_MBMT) at util/virperf.c:241 241 return event->enabled; Problem is, shut off domains don't have priv->perf allocated. Therefore if in frame #1 qemuDomainGetStatsPerf() tries to check if perf events are enabled, NULL is passed to virPerfEventIsEnabled() which due to some incredible implementation dereference it. Fix this by checking whether passed object is not NULL. Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
2016-06-03 08:09:17 +00:00
static int
virPerfRdtAttrInit(void)
{
char *tmp = NULL;
unsigned int attr_type = 0;
g_autofree char *buf = NULL;
if (virFileReadAllQuiet("/sys/devices/intel_cqm/type", 10, &buf) < 0)
return -1;
if ((tmp = strchr(buf, '\n')))
*tmp = '\0';
if (virStrToLong_ui(buf, NULL, 10, &attr_type) < 0) {
virReportSystemError(errno, "%s",
_("failed to get rdt event type"));
return -1;
}
attrs[VIR_PERF_EVENT_CMT].attrType = attr_type;
attrs[VIR_PERF_EVENT_MBMT].attrType = attr_type;
attrs[VIR_PERF_EVENT_MBML].attrType = attr_type;
return 0;
}
int
virPerfEventEnable(virPerf *perf,
virPerfEventType type,
pid_t pid)
{
struct perf_event_attr attr;
struct virPerfEvent *event = &(perf->events[type]);
struct virPerfEventAttr *event_attr = &attrs[type];
if (event->enabled)
return 0;
if (event_attr->attrType == 0 && (type == VIR_PERF_EVENT_CMT ||
type == VIR_PERF_EVENT_MBMT ||
type == VIR_PERF_EVENT_MBML)) {
virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED,
_("unable to enable host cpu perf event for %s"),
virPerfEventTypeToString(type));
return -1;
}
if (type == VIR_PERF_EVENT_CMT) {
g_autofree char *buf = NULL;
if (virFileReadAll("/sys/devices/intel_cqm/events/llc_occupancy.scale",
10, &buf) < 0)
goto error;
if (virStrToLong_i(buf, NULL, 10, &event->efields.cmt.scale) < 0) {
virReportSystemError(errno, "%s",
_("failed to get cmt scaling factor"));
goto error;
}
}
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.inherit = 1;
attr.disabled = 1;
attr.enable_on_exec = 0;
attr.type = event_attr->attrType;
attr.config = event_attr->attrConfig;
event->fd = syscall(__NR_perf_event_open, &attr, pid, -1, -1, 0);
if (event->fd < 0) {
virReportSystemError(errno,
_("unable to open host cpu perf event for %s"),
virPerfEventTypeToString(type));
goto error;
}
if (ioctl(event->fd, PERF_EVENT_IOC_ENABLE) < 0) {
virReportSystemError(errno,
_("unable to enable host cpu perf event for %s"),
virPerfEventTypeToString(type));
goto error;
}
event->enabled = true;
return 0;
error:
VIR_FORCE_CLOSE(event->fd);
return -1;
}
int
virPerfEventDisable(virPerf *perf,
virPerfEventType type)
{
struct virPerfEvent *event = &(perf->events[type]);
if (!event->enabled)
return 0;
if (ioctl(event->fd, PERF_EVENT_IOC_DISABLE) < 0) {
virReportSystemError(errno,
_("unable to disable host cpu perf event for %s"),
virPerfEventTypeToString(type));
return -1;
}
event->enabled = false;
VIR_FORCE_CLOSE(event->fd);
return 0;
}
bool virPerfEventIsEnabled(virPerf *perf,
virPerfEventType type)
{
return perf && perf->events[type].enabled;
}
int
virPerfReadEvent(virPerf *perf,
virPerfEventType type,
uint64_t *value)
{
struct virPerfEvent *event = &perf->events[type];
if (!event->enabled)
return -1;
if (saferead(event->fd, value, sizeof(uint64_t)) < 0) {
virReportSystemError(errno, "%s",
_("Unable to read cache data"));
return -1;
}
if (type == VIR_PERF_EVENT_CMT)
*value *= event->efields.cmt.scale;
return 0;
}
#else
static int
virPerfRdtAttrInit(void)
{
return 0;
}
int
virPerfEventEnable(virPerf *perf G_GNUC_UNUSED,
virPerfEventType type G_GNUC_UNUSED,
pid_t pid G_GNUC_UNUSED)
{
virReportSystemError(ENXIO, "%s",
_("Perf not supported on this platform"));
return -1;
}
int
virPerfEventDisable(virPerf *perf G_GNUC_UNUSED,
virPerfEventType type G_GNUC_UNUSED)
{
virReportSystemError(ENXIO, "%s",
_("Perf not supported on this platform"));
return -1;
}
bool
virPerfEventIsEnabled(virPerf *perf G_GNUC_UNUSED,
virPerfEventType type G_GNUC_UNUSED)
{
return false;
}
int
virPerfReadEvent(virPerf *perf G_GNUC_UNUSED,
virPerfEventType type G_GNUC_UNUSED,
uint64_t *value G_GNUC_UNUSED)
{
virReportSystemError(ENXIO, "%s",
_("Perf not supported on this platform"));
return -1;
}
#endif
virPerf *
virPerfNew(void)
{
size_t i;
virPerf *perf;
perf = g_new0(virPerf, 1);
for (i = 0; i < VIR_PERF_EVENT_LAST; i++) {
perf->events[i].fd = -1;
perf->events[i].enabled = false;
}
if (virPerfRdtAttrInit() < 0)
virResetLastError();
return perf;
}
void
virPerfFree(virPerf *perf)
{
size_t i;
if (perf == NULL)
return;
for (i = 0; i < VIR_PERF_EVENT_LAST; i++) {
if (perf->events[i].enabled)
virPerfEventDisable(perf, i);
}
g_free(perf);
}