Read only one NVidia’s performance counter through nv_perfmon

nv_perfmon is a tool developed by Ben Skeggs, it allows users to read some NVidia’s performance counters. Currently, only the NVE0 chipset is supported. nv_perfmon provides a ncurses interface in order to be more user-friendly and it displays the performance counters in a continuously mode. In order to only read once a counter, I wrote a little tool based on the original code of Ben Skeggs. That tool takes only one command line argument which is the name of the signal.

#include <core/device.h>
#include <core/class.h>

static struct nouveau_object *client;
static struct nouveau_object *device;
static char **signals;
static int nr_signals;

static void
trace_event(char *name, u32 *value)
{
        struct nv_perfctr_sample sample;
        struct nouveau_object *object;
        struct nv_perfctr_read read;
        int ret;

        ret = nouveau_object_new(client, 0x00000000, 0xc0000000,
                NV_PERFCTR_CLASS,
                &(struct nv_perfctr_class) {
                    .logic_op = 0xaaaa,
                    .signal[0].name = name,
                    .signal[0].size = strlen(name)
                }, sizeof(struct nv_perfctr_class),
                &object);
        assert(ret == 0);

        do {
                ret = nv_exec(object, NV_PERFCTR_SAMPLE, &sample, sizeof(sample));
                assert(ret == 0);

                ret = nv_exec(object, NV_PERFCTR_READ, &read, sizeof(read));
                assert(ret == 0 || ret == -EAGAIN);
                if (ret == 0) {
                        *value = read.ctr;
                }
        } while (ret == -EAGAIN);
}

int
main(int argc, char **argv)
{
	struct nv_perfctr_query args = {};
	struct nouveau_object *object;
        char *signal_name;
        u32 value;
	int ret;

        if (argc < 2) {
                fprintf(stderr, "Usage: %s <signal_name>\n", argv[0]);
                return 1;
        }
        signal_name = argv[1];

	ret = os_client_new(NULL, "error", argc, argv, &client);
	if (ret)
		return ret;

	ret = nouveau_object_new(client, 0xffffffff, 0x00000000,
				 NV_DEVICE_CLASS, &(struct nv_device_class) {
					.device = ~0ULL,
					.disable = ~(NV_DEVICE_DISABLE_MMIO |
						     NV_DEVICE_DISABLE_VBIOS |
						     NV_DEVICE_DISABLE_CORE |
						     NV_DEVICE_DISABLE_IDENTIFY),
					.debug0 = ~((1ULL << NVDEV_SUBDEV_TIMER) |
						    (1ULL << NVDEV_ENGINE_PERFMON)),
				}, sizeof(struct nv_device_class), &device);
	if (ret)
		return ret;

	ret = nouveau_object_new(client, 0x00000000, 0xdeadbeef,
				 NV_PERFCTR_CLASS, &(struct nv_perfctr_class) {
				 }, sizeof(struct nv_perfctr_class), &object);
	assert(ret == 0);
	do {
		u32 prev_iter = args.iter;

		args.name = NULL;
		ret = nv_exec(object, NV_PERFCTR_QUERY, &args, sizeof(args));
		assert(ret == 0);

		if (prev_iter) {
			nr_signals++;
			signals = realloc(signals, nr_signals * sizeof(char*));
			signals[nr_signals - 1] = malloc(args.size);

			args.iter = prev_iter;
			args.name = signals[nr_signals - 1];

			ret = nv_exec(object, NV_PERFCTR_QUERY,
				      &args, sizeof(args));
			assert(ret == 0);
		}
	} while (args.iter != 0xffffffff);

        nouveau_object_del(client, 0x00000000, 0xdeadbeef);

        trace_event(signal_name, &value);
        printf("Name  = %s\n", signal_name);
        printf("Value = %10u\n", value);

	while (nr_signals--)
		free(signals[nr_signals]);
	free(signals);
	return 0;
}