Commit 0c343af2 authored by Claire Jensen's avatar Claire Jensen Committed by Arnaldo Carvalho de Melo
Browse files

perf test: JSON format checking



Add field checking tests for perf stat JSON output.

Sanity checks the expected number of fields are present, that the
expected keys are present and they have the correct values.

Committer notes:

Had to fix this:

  -               $(INSTALL) tests/shell/lib/*.sh '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell/lib' \
  +               $(INSTALL) tests/shell/lib/*.sh '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell/lib'; \

Committer testing:

  [root@quaco ~]# perf test json
   90: perf stat JSON output linter                                    : Ok
  [root@quaco ~]# set -o vi
  [root@quaco ~]# perf test -v json
   90: perf stat JSON output linter                                    :
  --- start ---
  test child forked, pid 560794
  Checking json output: no args [Success]
  Checking json output: system wide [Success]
  Checking json output: system wide Checking json output: system wide no aggregation [Success]
  Checking json output: interval [Success]
  Checking json output: event [Success]
  Checking json output: per core [Success]
  Checking json output: per thread [Success]
  Checking json output: per die [Success]
  Checking json output: per node [Success]
  Checking json output: per socket [Success]
  test child finished with 0
  ---- end ----
  perf stat JSON output linter: Ok
  [root@quaco ~]#

Signed-off-by: default avatarClaire Jensen <cjense@google.com>
Acked-by: default avatarNamhyung Kim <namhyung@kernel.org>
Tested-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alyssa Ross <hi@alyssa.is>
Cc: Claire Jensen <clairej735@gmail.com>
Cc: Florian Fischer <florian.fischer@muhq.space>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@arm.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Like Xu <likexu@tencent.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Sandipan Das <sandipan.das@amd.com>
Cc: Stephane Eranian <eranian@google.com>
Cc: Xing Zhengjun <zhengjun.xing@linux.intel.com>
Link: https://lore.kernel.org/r/20220805200105.2020995-3-irogers@google.com


Signed-off-by: default avatarIan Rogers <irogers@google.com>
Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
parent df936cad
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -1005,7 +1005,8 @@ install-tests: all install-gtk
		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell'; \
		$(INSTALL) tests/shell/*.sh '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell'; \
		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell/lib'; \
		$(INSTALL) tests/shell/lib/*.sh '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell/lib'
		$(INSTALL) tests/shell/lib/*.sh '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell/lib'; \
		$(INSTALL) tests/shell/lib/*.py '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/shell/lib'

install-bin: install-tools install-tests install-traceevent-plugins

+96 −0
Original line number Diff line number Diff line
#!/usr/bin/python
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
# Basic sanity check of perf JSON output as specified in the man page.

import argparse
import sys
import json

ap = argparse.ArgumentParser()
ap.add_argument('--no-args', action='store_true')
ap.add_argument('--interval', action='store_true')
ap.add_argument('--system-wide-no-aggr', action='store_true')
ap.add_argument('--system-wide', action='store_true')
ap.add_argument('--event', action='store_true')
ap.add_argument('--per-core', action='store_true')
ap.add_argument('--per-thread', action='store_true')
ap.add_argument('--per-die', action='store_true')
ap.add_argument('--per-node', action='store_true')
ap.add_argument('--per-socket', action='store_true')
args = ap.parse_args()

Lines = sys.stdin.readlines()

def isfloat(num):
  try:
    float(num)
    return True
  except ValueError:
    return False


def isint(num):
  try:
    int(num)
    return True
  except ValueError:
    return False

def is_counter_value(num):
  return isfloat(num) or num == '<not counted>' or num == '<not supported>'

def check_json_output(expected_items):
  if expected_items != -1:
    for line in Lines:
      if 'failed' not in line:
        count = 0
        count = line.count(',')
        if count != expected_items and count >= 1 and count <= 3 and 'metric-value' in line:
          # Events that generate >1 metric may have isolated metric
          # values and possibly other prefixes like interval, core and
          # aggregate-number.
          continue
        if count != expected_items:
          raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_items}'
                             f' in \'{line}\'')
  checks = {
      'aggregate-number': lambda x: isfloat(x),
      'core': lambda x: True,
      'counter-value': lambda x: is_counter_value(x),
      'cgroup': lambda x: True,
      'cpu': lambda x: isint(x),
      'die': lambda x: True,
      'event': lambda x: True,
      'event-runtime': lambda x: isfloat(x),
      'interval': lambda x: isfloat(x),
      'metric-unit': lambda x: True,
      'metric-value': lambda x: isfloat(x),
      'node': lambda x: True,
      'pcnt-running': lambda x: isfloat(x),
      'socket': lambda x: True,
      'thread': lambda x: True,
      'unit': lambda x: True,
  }
  input = '[\n' + ','.join(Lines) + '\n]'
  for item in json.loads(input):
    for key, value in item.items():
      if key not in checks:
        raise RuntimeError(f'Unexpected key: key={key} value={value}')
      if not checks[key](value):
        raise RuntimeError(f'Check failed for: key={key} value={value}')


try:
  if args.no_args or args.system_wide or args.event:
    expected_items = 6
  elif args.interval or args.per_thread or args.system_wide_no_aggr:
    expected_items = 7
  elif args.per_core or args.per_socket or args.per_node or args.per_die:
    expected_items = 8
  else:
    # If no option is specified, don't check the number of items.
    expected_items = -1
  check_json_output(expected_items)
except:
  print('Test failed for input:\n' + '\n'.join(Lines))
  raise
+147 −0
Original line number Diff line number Diff line
#!/bin/bash
# perf stat JSON output linter
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
# Checks various perf stat JSON output commands for the
# correct number of fields.

set -e

pythonchecker=$(dirname $0)/lib/perf_json_output_lint.py
if [ "x$PYTHON" == "x" ]
then
	if which python3 > /dev/null
	then
		PYTHON=python3
	elif which python > /dev/null
	then
		PYTHON=python
	else
		echo Skipping test, python not detected please set environment variable PYTHON.
		exit 2
	fi
fi

# Return true if perf_event_paranoid is > $1 and not running as root.
function ParanoidAndNotRoot()
{
	 [ $(id -u) != 0 ] && [ $(cat /proc/sys/kernel/perf_event_paranoid) -gt $1 ]
}

check_no_args()
{
	echo -n "Checking json output: no args "
	perf stat -j true 2>&1 | $PYTHON $pythonchecker --no-args
	echo "[Success]"
}

check_system_wide()
{
	echo -n "Checking json output: system wide "
	if ParanoidAndNotRoot 0
	then
		echo "[Skip] paranoia and not root"
		return
	fi
	perf stat -j -a true 2>&1 | $PYTHON $pythonchecker --system-wide
	echo "[Success]"
}

check_system_wide_no_aggr()
{
	echo -n "Checking json output: system wide "
	if ParanoidAndNotRoot 0
	then
		echo "[Skip] paranoia and not root"
		return
	fi
	echo -n "Checking json output: system wide no aggregation "
	perf stat -j -A -a --no-merge true 2>&1 | $PYTHON $pythonchecker --system-wide-no-aggr
	echo "[Success]"
}

check_interval()
{
	echo -n "Checking json output: interval "
	perf stat -j -I 1000 true 2>&1 | $PYTHON $pythonchecker --interval
	echo "[Success]"
}


check_event()
{
	echo -n "Checking json output: event "
	perf stat -j -e cpu-clock true 2>&1 | $PYTHON $pythonchecker --event
	echo "[Success]"
}

check_per_core()
{
	echo -n "Checking json output: per core "
	if ParanoidAndNotRoot 0
	then
		echo "[Skip] paranoia and not root"
		return
	fi
	perf stat -j --per-core -a true 2>&1 | $PYTHON $pythonchecker --per-core
	echo "[Success]"
}

check_per_thread()
{
	echo -n "Checking json output: per thread "
	if ParanoidAndNotRoot 0
	then
		echo "[Skip] paranoia and not root"
		return
	fi
	perf stat -j --per-thread -a true 2>&1 | $PYTHON $pythonchecker --per-thread
	echo "[Success]"
}

check_per_die()
{
	echo -n "Checking json output: per die "
	if ParanoidAndNotRoot 0
	then
		echo "[Skip] paranoia and not root"
		return
	fi
	perf stat -j --per-die -a true 2>&1 | $PYTHON $pythonchecker --per-die
	echo "[Success]"
}

check_per_node()
{
	echo -n "Checking json output: per node "
	if ParanoidAndNotRoot 0
	then
		echo "[Skip] paranoia and not root"
		return
	fi
	perf stat -j --per-node -a true 2>&1 | $PYTHON $pythonchecker --per-node
	echo "[Success]"
}

check_per_socket()
{
	echo -n "Checking json output: per socket "
	if ParanoidAndNotRoot 0
	then
		echo "[Skip] paranoia and not root"
		return
	fi
	perf stat -j --per-socket -a true 2>&1 | $PYTHON $pythonchecker --per-socket
	echo "[Success]"
}

check_no_args
check_system_wide
check_system_wide_no_aggr
check_interval
check_event
check_per_core
check_per_thread
check_per_die
check_per_node
check_per_socket
exit 0