All logging should use call new functions

Signed-off-by: colramos-amd <colramos@amd.com>


[ROCm/rocprofiler-compute commit: 5bf38a4fed]
Este commit está contenido en:
colramos-amd
2024-01-30 17:25:16 -06:00
cometido por Karl W. Schulz
padre cfdf288cba
commit a1371462ba
Se han modificado 26 ficheros con 474 adiciones y 337 borrados
@@ -23,12 +23,11 @@
##############################################################################el
from abc import ABC, abstractmethod
from utils.utils import error, is_workload_empty, demarcate
from utils.utils import is_workload_empty, demarcate, console_error, console_log, console_warning, console_debug
from pymongo import MongoClient
from tqdm import tqdm
import os
import logging
import getpass
import pandas as pd
@@ -63,7 +62,7 @@ class DatabaseConnector:
soc = sys_info["name"][0]
name = sys_info["workload_name"][0]
else:
error("[database] Unable to parse SoC and/or workload name from sysinfo.csv")
console_error("[database] Unable to parse SoC and/or workload name from sysinfo.csv")
self.connection_info["db"] = (
"omniperf_" + str(self.args.team) + "_" + str(name) + "_" + str(soc)
@@ -76,10 +75,9 @@ class DatabaseConnector:
file = "blank"
for file in tqdm(os.listdir(self.connection_info["workload"])):
if file.endswith(".csv"):
logging.info(
"[database] Uploading: %s" % self.connection_info["workload"]
+ "/"
+ file
console_log(
"database",
"Uploading: %s" % self.connection_info["workload"] + "/" + file
)
try:
fileName = file[0 : file.find(".")]
@@ -97,15 +95,21 @@ class DatabaseConnector:
os.system(cmd)
i += 1
except pd.errors.EmptyDataError:
logging.info("[database] Skipping empty file: %s" % file)
console_warning("[database] Skipping empty file: %s" % file)
logging.info("[database] %s collections successfully added." % i)
console_log(
"database",
"%s collections successfully added." % i
)
mydb = self.client["workload_names"]
mycol = mydb["names"]
value = {"name": self.connection_info["db"]}
newValue = {"name": self.connection_info["db"]}
mycol.replace_one(value, newValue, upsert=True)
logging.info("[database] Workload name uploaded.")
console_log(
"database",
"Workload name uploaded."
)
@demarcate
def db_remove(self):
@@ -116,63 +120,60 @@ class DatabaseConnector:
self.client.drop_database(db_to_remove)
db = self.client["workload_names"]
col = db["names"]
col.delete_many({"name": self.connection_info["workload"]})
col.delete_many({"name": self.connection_info['workload']})
logging.info(
"[database] Successfully removed %s" % self.connection_info["workload"]
console_log(
"database",
"Successfully removed %s" % self.connection_info['workload']
)
@abstractmethod
def pre_processing(self):
"""Perform any pre-processing steps prior to database conncetion."""
logging.debug("[database] pre-processing database connection")
"""Perform any pre-processing steps prior to database conncetion.
"""
console_debug(
"database",
"pre-processing database connection"
)
if not self.args.remove and not self.args.upload:
error("Either -i/--import or -r/--remove is required in database mode")
self.interaction_type = "import" if self.args.upload else "remove"
console_error("Either -i/--import or -r/--remove is required in database mode")
self.interaction_type = 'import' if self.args.upload else 'remove'
# Detect interaction type
if self.interaction_type == "remove":
logging.debug("[database] validating arguments for --remove workflow")
if self.interaction_type == 'remove':
console_debug(
"database",
"validating arguments for --remove workflow"
)
is_full_workload_name = self.args.workload.count("_") >= 3
if not is_full_workload_name:
error(
"-w/--workload is not valid. Please use full workload name as seen in GUI when removing (i.e. omniperf_asw_vcopy_mi200)"
)
if (
self.connection_info["host"] == None
or self.connection_info["username"] == None
):
error(
"-H/--host and -u/--username are required when interaction type is set to %s"
% self.interaction_type
)
if (
self.connection_info["workload"] == "admin"
or self.connection_info["workload"] == "local"
):
error("Cannot remove %s. Try again." % self.connection_info["workload"])
console_error("-w/--workload is not valid. Please use full workload name as seen in GUI when removing (i.e. omniperf_asw_vcopy_mi200)")
if self.connection_info['host'] == None or self.connection_info['username'] == None:
console_error("-H/--host and -u/--username are required when interaction type is set to %s" % self.interaction_type)
if self.connection_info['workload'] == "admin" or self.connection_info['workload'] == "local":
console_error("Cannot remove %s. Try again." % self.connection_info['workload'])
else:
logging.debug("[database] validating arguments for --import workflow")
console_debug(
"database",
"validating arguments for --import workflow"
)
if (
self.connection_info["host"] == None
or self.connection_info["team"] == None
or self.connection_info["username"] == None
or self.connection_info["workload"] == None
):
error(
"-H/--host, -w/--workload, -u/--username, and -t/--team are all required when interaction type is set to %s"
% self.interaction_type
)
console_error("-H/--host, -w/--workload, -u/--username, and -t/--team are all required when interaction type is set to %s" % self.interaction_type)
if os.path.isdir(os.path.abspath(self.connection_info["workload"])):
is_workload_empty(self.connection_info["workload"])
else:
error("--workload is invalid. Please pass path to a valid directory.")
console_error("--workload is invalid. Please pass path to a valid directory.")
if len(self.args.team) > 13:
error("--team exceeds 13 character limit. Try again.")
console_error("--team exceeds 13 character limit. Try again.")
# format path properly
self.connection_info["workload"] = os.path.abspath(
self.connection_info["workload"]
@@ -183,9 +184,15 @@ class DatabaseConnector:
try:
self.connection_info["password"] = getpass.getpass()
except Exception as e:
error("[database] PASSWORD ERROR %s" % e)
console_error(
"database",
"PASSWORD ERROR %s" % e
)
else:
logging.info("[database] Password recieved")
console_log(
"database",
"Password recieved"
)
else:
password = self.connection_info["password"]
@@ -207,4 +214,10 @@ class DatabaseConnector:
try:
self.client.server_info()
except:
error("[database] Unable to connect to the DB server.")
console_error(
"database",
"Unable to connect to the DB server."
)
@@ -33,8 +33,8 @@ import collections
from collections import OrderedDict
from pathlib import Path
from utils import schema
from utils.utils import console_debug, console_error
import config
import logging
# TODO: use pandas chunksize or dask to read really large csv file
# from dask import dataframe as dd
@@ -173,9 +173,8 @@ def create_df_pmc(raw_data_dir, verbose):
dfs.append(tmp_df)
coll_levels.append(f[:-4])
final_df = pd.concat(dfs, keys=coll_levels, axis=1, copy=False)
# TODO: join instead of concat!
if verbose >= 2:
print("pmc_raw_data final_df ", final_df.info())
console_debug("pmc_raw_data final_df $s" % final_df.info())
return final_df
@@ -231,5 +230,4 @@ def is_single_panel_config(root_dir, supported_archs):
elif counter == len(supported_archs):
return False
else:
logging.error("Found multiple panel config sets but incomplete for all archs!")
sys.exit(1)
console_error("Found multiple panel config sets but incomplete for all archs.")
+2 -6
Ver fichero
@@ -29,6 +29,7 @@ import plotly.express as px
import colorlover
from utils import schema
from utils.utils import console_error
pd.set_option(
"mode.chained_assignment", None
@@ -243,12 +244,7 @@ def build_bar_chart(display_df, table_config, barchart_elements, norm_filt):
).update_xaxes(range=[0, 110])
)
else:
print(
"ERROR: Table id {}. Cannot determine barchart type.".format(
table_config["id"]
)
)
sys.exit(-1)
console_error("Table id %s. Cannot determine barchart type." % table_config["id"])
# update layout for each of the charts
for fig in d_figs:
@@ -26,14 +26,14 @@ import sys
from dash import html
from dash_svg import Svg, G, Path, Rect, Text
from utils.utils import console_error
hidden_columns = ["Tips", "coll_level"]
def insert_chart_data(mem_data, base_data):
if len(mem_data) != 1:
print("Memory Chart config doesn't follow expected formatting")
sys.exit(1)
console_error("Memory Chart config doesn't follow expected formatting")
table_config = mem_data[0]["metric_table"]
@@ -23,14 +23,12 @@
##############################################################################el
import os
import sys
import logging
import glob
import glob
import re
import subprocess
import pandas as pd
from utils.utils import error
from utils.utils import console_error, console_debug, console_log
cache = dict()
@@ -123,7 +121,7 @@ def kernel_name_shortener(workload_dir, level):
if level < 5:
cpp_filt = os.path.join("/usr", "bin", "c++filt")
if not os.path.isfile(cpp_filt):
error("Could not resolve c++filt in expected directory: %s" % cpp_filt)
console_error("Could not resolve c++filt in expected directory: %s" % cpp_filt)
for fpath in glob.glob(workload_dir + "/[SQpmc]*.csv"):
try:
@@ -135,8 +133,12 @@ def kernel_name_shortener(workload_dir, level):
modified_df = shorten_file(orig_df, level)
modified_df.to_csv(fpath, index=False)
except pd.errors.EmptyDataError:
logging.debug(
"[profiling] Skipping shortening on empty csv: %s" % str(fpath)
console_debug(
"profiling",
"Skipping shortening on empty csv: %s" % str(fpath)
)
logging.info("[profiling] Kernel_Name shortening complete.")
console_log(
"profiling",
"Kernel_Name shortening complete."
)
+11 -16
Ver fichero
@@ -27,13 +27,12 @@ import sys
import astunparse
import re
import os
import warnings
import pandas as pd
import numpy as np
from utils import schema
from utils.utils import error
from utils.utils import console_warning, console_error
from pathlib import Path
import logging
import warnings
# ------------------------------------------------------------------------------
# Internal global definitions
@@ -420,8 +419,7 @@ def calc_builtin_var(var, sys_info):
elif isinstance(var, str) and var.startswith("$total_l2_chan"):
return sys_info.total_l2_chan
else:
print("Don't support", var)
sys.exit(1)
console_error("Built-in var \" %s \" is not supported" % var)
def build_dfs(archConfigs, filter_metrics, sys_info):
@@ -679,7 +677,8 @@ def eval_metric(dfs, dfs_type, sys_info, raw_pmc_df, debug):
and hasattr(raw_pmc_df["pmc_perf"], "GRBM_GUI_ACTIVE")
and (raw_pmc_df["pmc_perf"]["GRBM_GUI_ACTIVE"] == 0).any()
):
error("Dectected GRBM_GUI_ACTIVE == 0\nHaulting execution.")
console_warning("Dectected GRBM_GUI_ACTIVE == 0")
console_error("Hauting execution for warning above.")
ammolite__se_per_gpu = sys_info.se_per_gpu
ammolite__pipes_per_gpu = sys_info.pipes_per_gpu
@@ -859,7 +858,7 @@ def apply_filters(workload, dir, is_gui, debug):
kernels_df = pd.read_csv(os.path.join(dir, "pmc_kernel_top.csv"))
for kernel_id in workload.filter_kernel_ids:
if kernel_id >= len(kernels_df["Kernel_Name"]):
error(
console_error(
"{} is an invalid kernel id. Please enter an id between 0-{}".format(
kernel_id, len(kernels_df["Kernel_Name"]) - 1
)
@@ -885,7 +884,7 @@ def apply_filters(workload, dir, is_gui, debug):
)
ret_df = ret_df.loc[df_cleaned.isin(workload.filter_kernel_ids)]
else:
error("Mixing kernel indices and string filters is not currently supported")
console_error("analyze", "Mixing kernel indices and string filters is not currently supported")
if workload.filter_dispatch_ids:
# NB: support ignoring the 1st n dispatched execution by '> n'
@@ -922,9 +921,7 @@ def load_kernel_top(workload, dir):
if file.exists():
tmp[id] = pd.read_csv(file)
else:
logging.info(
"Warning: Issue loading top kernels. Check pmc_kernel_top.csv"
)
console_warning("Issue loading top kernels. Check pmc_kernel_top.csv")
# NB: Special case for sysinfo. Probably room for improvement in this whole function design
elif "from_csv_columnwise" in df.columns and id == 101:
tmp[id] = workload.sys_info.transpose()
@@ -942,9 +939,7 @@ def load_kernel_top(workload, dir):
# so tty could detect them and show them correctly in comparison.
tmp[id].columns = ["Info"]
else:
logging.info(
"Warning: Issue loading top kernels. Check pmc_kernel_top.csv"
)
console_warning("Issue loading top kernels. Check pmc_kernel_top.csv")
workload.dfs.update(tmp)
@@ -988,8 +983,8 @@ def correct_sys_info(mspec, specs_correction: dict):
for k, v in pairs.items():
if not hasattr(mspec, str(k)):
error(
f"Invalid specs correction '{k}'. Please use --specs option to peak valid specs"
console_error(
"analyze", f"Invalid specs correction '{k}'. Please use --specs option to peak valid specs"
)
setattr(mspec, str(k), v)
return mspec.get_class_members()
@@ -25,7 +25,7 @@
import os
from dataclasses import dataclass
import logging
from utils.utils import console_debug
import csv
################################################
@@ -112,8 +112,11 @@ def calc_ceilings(roofline_parameters, dtype, benchmark_data):
if dtype != "FP16" and dtype != "I8":
peakOps = float(benchmark_data[dtype + "Flops"][roofline_parameters["device_id"]])
for i in range(0, len(cacheHierarchy)):
# Plot BW line
logging.debug("[roofline] Current cache level is %s" % cacheHierarchy[i])
# Plot BW line
console_debug(
"roofline"
"Current cache level is %s" % cacheHierarchy[i]
)
curr_bw = cacheHierarchy[i] + "Bw"
peakBw = float(benchmark_data[curr_bw][roofline_parameters["device_id"]])
@@ -143,9 +146,12 @@ def calc_ceilings(roofline_parameters, dtype, benchmark_data):
y2_mfma = peakMFMA
# These are the points to use:
logging.debug("[roofline] coordinate points:")
logging.debug("x = [{}, {}]".format(x1, x2_mfma))
logging.debug("y = [{}, {}]".format(y1, y2_mfma))
console_debug(
"roofline",
"coordinate points:"
)
console_debug("x = [{}, {}]".format(x1, x2_mfma))
console_debug("y = [{}, {}]".format(y1, y2_mfma))
graphPoints[cacheHierarchy[i].lower()].append([x1, x2_mfma])
graphPoints[cacheHierarchy[i].lower()].append([y1, y2_mfma])
@@ -161,7 +167,7 @@ def calc_ceilings(roofline_parameters, dtype, benchmark_data):
if x2 < x0:
x0 = x2
logging.debug("FMA ROOF [{}, {}], [{},{}]".format(x0, XMAX, peakOps, peakOps))
console_debug("FMA ROOF [{}, {}], [{},{}]".format(x0, XMAX, peakOps, peakOps))
graphPoints["valu"].append([x0, XMAX])
graphPoints["valu"].append([peakOps, peakOps])
graphPoints["valu"].append(peakOps)
@@ -174,9 +180,7 @@ def calc_ceilings(roofline_parameters, dtype, benchmark_data):
if x2_mfma < x0_mfma:
x0_mfma = x2_mfma
logging.debug(
"MFMA ROOF [{}, {}], [{},{}]".format(x0_mfma, XMAX, peakMFMA, peakMFMA)
)
console_debug("MFMA ROOF [{}, {}], [{},{}]".format(x0_mfma, XMAX, peakMFMA, peakMFMA))
graphPoints["mfma"].append([x0_mfma, XMAX])
graphPoints["mfma"].append([peakMFMA, peakMFMA])
graphPoints["mfma"].append(peakMFMA)
@@ -253,10 +257,9 @@ def calc_ai(sort_type, ret_df):
+ (df["SQ_INSTS_VALU_MFMA_MOPS_F64"][idx] * 512)
)
except KeyError:
logging.debug(
"[roofline] {}: Skipped total_flops at index {}".format(
kernelName[:35], idx
)
console_debug(
"roofline",
"{}: Skipped total_flops at index {}".format(kernelName[:35], idx)
)
pass
try:
@@ -284,9 +287,9 @@ def calc_ai(sort_type, ret_df):
)
)
except KeyError:
logging.debug(
"{}: Skipped valu_flops at index {}".format(kernelName[:35], idx)
)
console_debug(
"roofline",
"{}: Skipped valu_flops at index {}".format(kernelName[:35], idx))
pass
try:
@@ -296,8 +299,9 @@ def calc_ai(sort_type, ret_df):
mfma_flops_f64 += df["SQ_INSTS_VALU_MFMA_MOPS_F64"][idx] * 512
mfma_iops_i8 += df["SQ_INSTS_VALU_MFMA_MOPS_I8"][idx] * 512
except KeyError:
logging.debug(
"[roofline] {}: Skipped mfma ops at index {}".format(kernelName[:35], idx)
console_debug(
"roofline",
"{}: Skipped mfma ops at index {}".format(kernelName[:35], idx)
)
pass
@@ -308,18 +312,18 @@ def calc_ai(sort_type, ret_df):
* L2_BANKS
) # L2_BANKS = 32 (since assuming mi200)
except KeyError:
logging.debug(
"[roofline] {}: Skipped lds_data at index {}".format(kernelName[:35], idx)
console_debug(
"roofline",
"{}: Skipped lds_data at index {}".format(kernelName[:35], idx)
)
pass
try:
L1cache_data += df["TCP_TOTAL_CACHE_ACCESSES_sum"][idx] * 64
except KeyError:
logging.debug(
"[roofline] {}: Skipped L1cache_data at index {}".format(
kernelName[:35], idx
)
console_debug(
"roofline",
"{}: Skipped L1cache_data at index {}".format(kernelName[:35], idx)
)
pass
@@ -331,10 +335,9 @@ def calc_ai(sort_type, ret_df):
+ df["TCP_TCC_READ_REQ_sum"][idx] * 64
)
except KeyError:
logging.debug(
"[roofline] {}: Skipped L2cache_data at index {}".format(
kernelName[:35], idx
)
console_debug(
"roofline",
"{}: Skipped L2cache_data at index {}".format(kernelName[:35], idx)
)
pass
try:
@@ -345,8 +348,9 @@ def calc_ai(sort_type, ret_df):
+ ((df["TCC_EA_WRREQ_sum"][idx] - df["TCC_EA_WRREQ_64B_sum"][idx]) * 32)
)
except KeyError:
logging.debug(
"[roofline] {}: Skipped hbm_data at index {}".format(kernelName[:35], idx)
console_debug(
"roofline",
"{}: Skipped hbm_data at index {}".format(kernelName[:35], idx)
)
pass
@@ -375,7 +379,7 @@ def calc_ai(sort_type, ret_df):
avgDuration / calls,
)
)
logging.debug(
console_debug(
"Just added {} to AI_Data at index {}. # of calls: {}".format(
kernelName, idx, calls
)
+20 -25
Ver fichero
@@ -30,7 +30,6 @@ import sys
import socket
import subprocess
import importlib
import logging
import config
import pandas as pd
@@ -38,7 +37,7 @@ from datetime import datetime
from math import ceil
from dataclasses import dataclass, field, fields
from pathlib import Path as path
from utils.utils import error, get_hbm_stack_num, get_version
from utils.utils import get_hbm_stack_num, get_version, console_error, console_warning, console_log
from utils.tty import get_table_string
VERSION_LOC = [
@@ -64,7 +63,7 @@ def detect_arch(_rocminfo):
gpu_arch = str(gpu_arch)
break
if not gpu_arch in SUPPORTED_ARCHS.keys():
error("[profiling] Cannot find a supported arch in rocminfo")
console_error("Cannot find a supported arch in rocminfo")
else:
return (gpu_arch, idx1)
@@ -84,12 +83,12 @@ def generate_machine_specs(args, sysinfo: dict = None):
try:
sysinfo_ver = str(sysinfo["version"])
except KeyError:
error(
console_error(
"Detected mismatch in sysinfo versioning. You need to reprofile to update data."
)
version = get_version(config.omniperf_home)["version"]
if sysinfo_ver != version[: version.find(".")]:
error(
console_error(
"Detected mismatch in sysinfo versioning. You need to reprofile to update data."
)
return MachineSpecs(**sysinfo)
@@ -172,7 +171,7 @@ def generate_machine_specs(args, sysinfo: dict = None):
try:
soc_module = importlib.import_module("omniperf_soc.soc_" + specs.gpu_arch)
except ModuleNotFoundError as e:
error(
console_error(
"Arch %s marked as supported, but couldn't find class implementation %s."
% (specs.gpu_arch, e)
)
@@ -513,16 +512,15 @@ class MachineSpecs:
):
pass
else:
# TODO: use proper logging function when that's merged
logging.warning(
f"WARNING: Incomplete class definition for {self.gpu_arch}. "
console_warning(
f"Incomplete class definition for {self.gpu_arch}. "
f"Expecting populated {name} but detected None."
)
all_populated = False
data[name] = value
if not all_populated:
error("Missing specs fields for %s" % self.gpu_arch)
console_error("Missing specs fields for %s" % self.gpu_arch)
return pd.DataFrame(data, index=[0])
def __repr__(self):
@@ -539,7 +537,7 @@ class MachineSpecs:
if name == "version":
topstr += f"Output version: {value}\n"
else:
error(f"Unknown out of table printing field: {name}")
console_error(f"Unknown out of table printing field: {name}")
continue
if "name" in field.metadata:
name = field.metadata["name"]
@@ -573,17 +571,16 @@ def get_rocm_ver():
# check if ROCM_VER is supplied externally
ROCM_VER_USER = os.getenv("ROCM_VER")
if ROCM_VER_USER is not None:
logging.info(
"Overriding missing ROCm version detection with ROCM_VER = %s"
% ROCM_VER_USER
console_log(
"profiling",
"Overriding missing ROCm version detection with ROCM_VER = %s" % ROCM_VER_USER
)
rocm_ver = ROCM_VER_USER
else:
_rocm_path = os.getenv("ROCM_PATH", "/opt/rocm")
error(
"Unable to detect a complete local ROCm installation.\nThe expected %s/.info/ versioning directory is missing. Please ensure you have valid ROCm installation."
% _rocm_path
)
console_warning("Unable to detect a complete local ROCm installation.")
console_warning("The expected %s/.info/ versioning directory is missing." % _rocm_path)
console_error("Ensure you have valid ROCm installation.")
return rocm_ver
@@ -591,18 +588,16 @@ def run(cmd, exit_on_error=False):
try:
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except FileNotFoundError as e:
error(
console_error(
f"Unable to parse specs. Can't find ROCm asset: {e.filename}\nTry passing a path to an existing workload results in 'analyze' mode."
)
if exit_on_error:
if cmd[0] == "rocm-smi":
if p.returncode != 2 and p.returncode != 0:
logging.error("ERROR: No GPU detected. Unable to load rocm-smi")
sys.exit(1)
console_error("No GPU detected. Unable to load rocm-smi")
elif p.returncode != 0:
logging.error("ERROR: command [%s] failed with non-zero exit code" % cmd)
sys.exit(1)
console_error("Command [%s] failed with non-zero exit code" % cmd)
return p.stdout.decode("utf-8")
@@ -638,7 +633,7 @@ def total_xcds(archname, compute_partition):
mi300a_archs = ["mi300a_a0", "mi300a_a1"]
mi300x_archs = ["mi300x_a0", "mi300x_a1"]
if archname.lower() in mi300a_archs + mi300x_archs and compute_partition == "NA":
error("Invalid compute partition found for {}".format(archname))
console_error("Invalid compute partition found for {}".format(archname))
if archname.lower() not in mi300a_archs + mi300x_archs:
return 1
# from the whitepaper
@@ -660,7 +655,7 @@ def total_xcds(archname, compute_partition):
if compute_partition.lower() == "cpx":
if archname.lower() in mi300x_archs:
return 2
error(
console_error(
"Unknown compute partition / arch found for {} / {}".format(
compute_partition, archname
)
+4 -9
Ver fichero
@@ -25,10 +25,10 @@
import pandas as pd
from pathlib import Path
from tabulate import tabulate
import sys
import copy
from utils import parser
from utils.utils import console_warning, console_log
hidden_columns = ["Tips", "coll_level"]
hidden_sections = [1900, 2000]
@@ -135,7 +135,7 @@ def show_all(args, runs, archConfigs, output):
0, 1
)
if args.verbose >= 2:
print("---------", header, t_df)
console_log("---------", header, t_df)
t_df_pretty = (
t_df.astype(float)
@@ -168,13 +168,8 @@ def show_all(args, runs, archConfigs, output):
violation_idx = t_df_pretty.index[
t_df_pretty.abs() > args.report_diff
]
print(
"DEBUG ERROR: Dataframe diff exceeds {} threshold requirement\nSee metric {}".format(
str(args.report_diff) + "%",
violation_idx.to_numpy(),
)
)
print(df)
console_warning("Dataframe diff exceeds %s threshold requirement\nSee metric %s" % (str(args.report_diff) + "%", violation_idx.to_numpy()))
console_warning(df)
else:
cur_df_copy = copy.deepcopy(cur_df)