saidump/SaiDump.cpp (452 lines of code) (raw):
#include "SaiDump.h"
extern "C" {
#include <sai.h>
}
#include <inttypes.h>
#include <set>
#include <regex>
#include <climits>
#include <getopt.h>
#include "sairediscommon.h"
using namespace swss;
using json = nlohmann::json;
using namespace syncd;
SaiDump::SaiDump()
{
}
SaiDump::~SaiDump()
{
}
void SaiDump::printUsage()
{
SWSS_LOG_ENTER();
std::cout << "Usage: saidump [-t] [-g] [-r] [-m] [-h]" << std::endl;
std::cout << " -t --tempView:" << std::endl;
std::cout << " Dump temp view" << std::endl;
std::cout << " -g --dumpGraph:" << std::endl;
std::cout << " Dump current graph" << std::endl;
std::cout << " -r --rdb:" << std::endl;
std::cout << " Dump by parsing the RDB JSON file, which is created based on Redis dump.rdb that is generated by redis-cli --rdb command" << std::endl;
std::cout << " -m --max:" << std::endl;
std::cout << " Config the the RDB JSON file's max size in MB, which is optional with default value 100MB" << std::endl;
std::cout << " -h --help:" << std::endl;
std::cout << " Print out this message" << std::endl;
}
void SaiDump::handleCmdLine(int argc, char **argv)
{
SWSS_LOG_ENTER();
// Default value: 100MB
static constexpr int64_t RDB_JSON_MAX_SIZE = 1024 * 1024 * 100;
dumpTempView = false;
dumpGraph = false;
rdbJSonSizeLimit = RDB_JSON_MAX_SIZE;
const char* const optstring = "gtr:m:h";
uint64_t result = 0;
while (true)
{
static struct option long_options[] =
{
{ "dumpGraph", no_argument, 0, 'g' },
{ "tempView", no_argument, 0, 't' },
{ "rdb", required_argument, 0, 'r' },
{ "max", required_argument, 0, 'm' },
{ "help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
int option_index = 0;
int c = getopt_long(argc, argv, optstring, long_options, &option_index);
if (c == -1)
{
break;
}
switch (c)
{
case 'g':
SWSS_LOG_NOTICE("Dumping graph");
dumpGraph = true;
break;
case 't':
SWSS_LOG_NOTICE("Dumping temp view");
dumpTempView = true;
break;
case 'r':
SWSS_LOG_NOTICE("Dumping from %s", optarg);
rdbJsonFile = std::string(optarg);
break;
case 'm':
if(!regex_match(optarg, std::regex(R"([+]?\d+)"))) //only positive numeric chars are valid, such as 3984, +3232, etc.
{
SWSS_LOG_WARN("invalid option -m %s", optarg);
printUsage();
exit(EXIT_SUCCESS);
}
result = strtoull(optarg, NULL, 0);
if((errno == ERANGE && result == ULLONG_MAX) || result == 0 || result >= INT_MAX)
{
SWSS_LOG_WARN("invalid option -m %s", optarg);
printUsage();
exit(EXIT_SUCCESS);
}
rdbJSonSizeLimit = result * 1024 * 1024;
SWSS_LOG_NOTICE("Configure the RDB JSON MAX size to %llu MB", rdbJSonSizeLimit / 1024 / 1024);
break;
case 'h':
printUsage();
exit(EXIT_SUCCESS);
case '?':
SWSS_LOG_WARN("unknown option %c", optopt);
printUsage();
exit(EXIT_FAILURE);
default:
SWSS_LOG_ERROR("getopt_long failure");
exit(EXIT_FAILURE);
}
}
}
size_t SaiDump::getMaxAttrLen(const TableMap& map)
{
SWSS_LOG_ENTER();
size_t max = 0;
for (const auto&field: map)
{
max = std::max(max, field.first.length());
}
return max;
}
std::string SaiDump::padString(std::string s, size_t pad)
{
SWSS_LOG_ENTER();
size_t len = s.length();
if (len < pad)
{
s.insert(len, pad - len, ' ');
}
return s;
}
void SaiDump::printAttributes(size_t indent, const TableMap& map)
{
SWSS_LOG_ENTER();
size_t max_len = getMaxAttrLen(map);
std::string str_indent = padString("", indent);
for (const auto&field: map)
{
const sai_attr_metadata_t *meta;
sai_deserialize_attr_id(field.first, &meta);
std::stringstream ss;
ss << str_indent << padString(field.first, max_len) << " : ";
ss << field.second;
std::cout << ss.str() << std::endl;
}
}
// colors are in HSV
#define GV_ARROW_COLOR "0.650 0.700 0.700"
#define GV_ROOT_COLOR "0.650 0.200 1.000"
#define GV_NODE_COLOR "0.650 0.500 1.000"
void SaiDump::dumpGraphFun(const TableDump& td)
{
SWSS_LOG_ENTER();
std::map<sai_object_id_t, const sai_object_type_info_t*> oidtypemap;
std::map<sai_object_type_t,const sai_object_type_info_t*> typemap;
std::cout << "digraph \"SAI Object Dependency Graph\" {" << std::endl;
std::cout << "size = \"30,12\"; ratio = fill;" << std::endl;
std::cout << "node [ style = filled ];" << std::endl;
// build object type map first
std::set<sai_object_type_t> definedtypes;
std::map<sai_object_type_t, int> usagemap;
for (const auto& key: td)
{
sai_object_meta_key_t meta_key;
sai_deserialize_object_meta_key(key.first, meta_key);
auto info = sai_metadata_get_object_type_info(meta_key.objecttype);
typemap[info->objecttype] = info;
if (!info->isnonobjectid)
oidtypemap[meta_key.objectkey.key.object_id] = info;
if (definedtypes.find(meta_key.objecttype) != definedtypes.end())
continue;
definedtypes.insert(meta_key.objecttype);
}
std::set<std::string> definedlinks;
std::set<sai_object_type_t> ref;
std::set<sai_object_type_t> attrref;
#define SAI_OBJECT_TYPE_PREFIX_LEN (sizeof("SAI_OBJECT_TYPE_") - 1)
for (const auto& key: td)
{
sai_object_meta_key_t meta_key;
sai_deserialize_object_meta_key(key.first, meta_key);
auto info = sai_metadata_get_object_type_info(meta_key.objecttype);
// process non object id objects if any
for (size_t j = 0; j < info->structmemberscount; ++j)
{
const sai_struct_member_info_t *m = info->structmembers[j];
if (m->membervaluetype == SAI_ATTR_VALUE_TYPE_OBJECT_ID)
{
sai_object_id_t member_oid = m->getoid(&meta_key);
auto member_info = oidtypemap.at(member_oid);
if (member_info->objecttype == SAI_OBJECT_TYPE_SWITCH)
{
// skip link of SWITCH to non object id object types, since
// all of them contain switch_id
continue;
}
std::stringstream ss;
ss << std::string(member_info->objecttypename + SAI_OBJECT_TYPE_PREFIX_LEN) << " -> "
<< std::string(info->objecttypename + SAI_OBJECT_TYPE_PREFIX_LEN)
<< "[ color=\"" << GV_ARROW_COLOR << "\", style = dashed, penwidth = 2 ]";
std::string link = ss.str();
if (definedlinks.find(link) != definedlinks.end())
continue;
definedlinks.insert(link);
std::cout << link << std::endl;
}
}
// process attributes for this object
for (const auto&field: key.second)
{
const sai_attr_metadata_t *meta;
sai_deserialize_attr_id(field.first, &meta);
if (!meta->isoidattribute || meta->isreadonly)
{
// skip non oid attributes and read only attributes
continue;
}
sai_attribute_t attr;
sai_deserialize_attr_value(field.second, *meta, attr, false);
sai_object_list_t list = { 0, NULL };
switch (meta->attrvaluetype)
{
case SAI_ATTR_VALUE_TYPE_OBJECT_ID:
list.count = 1;
list.list = &attr.value.oid;
break;
case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_ID:
if (attr.value.aclfield.enable)
{
list.count = 1;
list.list = &attr.value.aclfield.data.oid;
}
break;
case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_ID:
if (attr.value.aclaction.enable)
{
list.count = 1;
list.list = &attr.value.aclaction.parameter.oid;
}
break;
case SAI_ATTR_VALUE_TYPE_OBJECT_LIST:
list = attr.value.objlist;
break;
case SAI_ATTR_VALUE_TYPE_ACL_FIELD_DATA_OBJECT_LIST:
if (attr.value.aclfield.enable)
list = attr.value.aclfield.data.objlist;
break;
case SAI_ATTR_VALUE_TYPE_ACL_ACTION_DATA_OBJECT_LIST:
if (attr.value.aclaction.enable)
list = attr.value.aclaction.parameter.objlist;
break;
default:
SWSS_LOG_THROW("attr value type: %d is not supported, FIXME", meta->attrvaluetype);
}
for (uint32_t i = 0; i < list.count; ++i)
{
sai_object_id_t oid = list.list[i];
if (oid == SAI_NULL_OBJECT_ID)
continue;
// this object type is not root, can be in the middle or leaf
ref.insert(info->objecttype);
auto attr_oid_info = oidtypemap.at(oid);
std::stringstream ss;
attrref.insert(attr_oid_info->objecttype);
ss << std::string(attr_oid_info->objecttypename + SAI_OBJECT_TYPE_PREFIX_LEN) << " -> "
<< std::string(info->objecttypename + SAI_OBJECT_TYPE_PREFIX_LEN)
<< "[ color = \"" << GV_ARROW_COLOR << "\" ]";
std::string link = ss.str();
if (definedlinks.find(link) != definedlinks.end())
continue;
definedlinks.insert(link);
std::cout << link << std::endl;
}
sai_deserialize_free_attribute_value(meta->attrvaluetype, attr);
}
}
for (auto t: typemap)
{
auto ot = t.first;
auto info = t.second;
auto name = std::string(info->objecttypename + SAI_OBJECT_TYPE_PREFIX_LEN);
if (info->isnonobjectid)
{
/* non object id leafs */
std::cout << name << " [ color = plum, shape = rect ];\n";
continue;
}
if (ref.find(ot) != ref.end() && attrref.find(ot) != attrref.end())
{
/* middle nodes */
std::cout << name << " [ color =\"" << GV_NODE_COLOR << "\" ];\n";
continue;
}
if (ref.find(ot) != ref.end() && attrref.find(ot) == attrref.end())
{
/* leafs */
std::cout << name << " [ color = palegreen, shape = rect ];\n";
continue;
}
if (ref.find(ot) == ref.end() && attrref.find(ot) != attrref.end())
{
/* roots */
std::cout << name << " [ color = \"" << GV_ROOT_COLOR << "\" ];\n";
continue;
}
/* objects which are there but not referenced nowhere for example STP */
std::cout << name << " [ color = \"" << GV_ROOT_COLOR << "\", shape = rect ];\n";
}
std::cout << "SWITCH -> PORT [ dir = none, color = red, peripheries = 2, penwidth = 2, style = dashed ];" << std::endl;
std::cout << "SWITCH [ color = orange, fillcolor = orange, shape = parallelogram, peripheries = 2 ];" << std::endl;
std::cout << "PORT [ color = gold, shape = diamond, peripheries = 2 ];" << std::endl;
std::cout << "}" << std::endl;
}
#define SWSS_LOG_ERROR_AND_STDERR(format, ...) { fprintf(stderr, format"\n", ##__VA_ARGS__); SWSS_LOG_ERROR(format, ##__VA_ARGS__); }
void SaiDump::traverseJson(const json & jsn)
{
SWSS_LOG_ENTER();
if (jsn.is_object())
{
for (auto it = jsn.begin(); it != jsn.end(); ++it)
{
std::string keystr = it.key();
std::string item_name = keystr;
size_t pos = keystr.find_first_of(":");
if (pos != std::string::npos)
{
if(ASIC_STATE_TABLE != keystr.substr(0, pos)) // filter out non "ASIC_STATE" items
{
continue;
}
item_name = keystr.substr(pos + 1);
if (item_name.find(":") != std::string::npos)
{
item_name.replace(item_name.find_first_of(":"), 1, " ");
}
}
else
{
continue;
}
std::cout << item_name << " " << std::endl;
json jsn_sub = it.value();
if (!it->is_object())
{
continue;
}
TableMap map;
for (auto it_sub = jsn_sub.begin(); it_sub != jsn_sub.end(); ++it_sub)
{
if (it_sub.key() != "NULL")
{
map[it_sub.key()] = it_sub.value();
}
}
constexpr size_t LINE_IDENT = 4;
size_t max_len = getMaxAttrLen(map);
std::string str_indent = padString("", LINE_IDENT);
for (const auto&field: map)
{
std::cout << str_indent << padString(field.first, max_len) << " : ";
std::cout << field.second << std::endl;
}
std::cout << std::endl;
}
}
else if(jsn.is_array())
{
for (const auto& element : jsn)
{
if (element.is_object() || element.is_array())
{
traverseJson(element);
}
}
}
}
sai_status_t SaiDump::dumpFromRedisRdbJson()
{
SWSS_LOG_ENTER();
std::ifstream input_file(rdbJsonFile);
if (!input_file.is_open())
{
SWSS_LOG_ERROR_AND_STDERR("The file %s does not exist for dumping from Redis RDB JSON file.", rdbJsonFile.c_str());
return SAI_STATUS_FAILURE;
}
try
{
// Parse the JSON data from the file (validation)
json jsonData;
input_file >> jsonData;
traverseJson(jsonData);
return SAI_STATUS_SUCCESS;
}
catch (std::exception &ex)
{
SWSS_LOG_ERROR_AND_STDERR("JSON parsing error: %s.", ex.what());
}
return SAI_STATUS_FAILURE;
}
void SaiDump::dumpGraphTable(const swss::TableDump &dump)
{
SWSS_LOG_ENTER();
for (const auto&key: dump)
{
auto start = key.first.find_first_of(":");
auto str_object_type = key.first.substr(0, start);
auto str_object_id = key.first.substr(start + 1);
sai_object_type_t object_type;
sai_deserialize_object_type(str_object_type, object_type);
auto info = sai_metadata_get_object_type_info(object_type);
if (!info->isnonobjectid)
{
sai_object_id_t object_id;
sai_deserialize_object_id(str_object_id, object_id);
mOidMap[object_id] = &key.second;
}
}
if (dumpGraph)
{
dumpGraphFun(dump);
return;
}
for (const auto&key: dump)
{
auto start = key.first.find_first_of(":");
auto str_object_type = key.first.substr(0, start);
auto str_object_id = key.first.substr(start + 1);
std::cout << str_object_type << " " << str_object_id << " " << std::endl;
size_t indent = 4;
printAttributes(indent, key.second);
std::cout << std::endl;
}
}
void SaiDump::dumpFromRedisDb(int argc, char **argv)
{
SWSS_LOG_ENTER();
std::string table = ASIC_STATE_TABLE;
handleCmdLine(argc, argv);
if (dumpTempView)
{
table = TEMP_PREFIX + table;
}
if (rdbJsonFile.size() > 0)
{
dumpFromRedisRdbJson();
return;
}
swss::DBConnector db("ASIC_DB", 0);
swss::Table t(&db, table);
TableDump dump;
t.dump(dump);
dumpGraphTable(dump);
}
std::string SaiDump::getRdbJsonFile()
{
SWSS_LOG_ENTER();
return rdbJsonFile;
}
uint64_t SaiDump::getRdbJSonSizeLimit()
{
SWSS_LOG_ENTER();
return rdbJSonSizeLimit;
}
bool SaiDump::getDumpTempView()
{
SWSS_LOG_ENTER();
return dumpTempView;
}
bool SaiDump::getDumpGraph()
{
SWSS_LOG_ENTER();
return dumpGraph;
}