Skip to content

Commit

Permalink
Initial work on JSON parser.
Browse files Browse the repository at this point in the history
  • Loading branch information
ashaduri committed Mar 6, 2023
1 parent c8f79c3 commit 7f688e0
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 9 deletions.
135 changes: 131 additions & 4 deletions src/applib/smartctl_ata_json_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ License: GNU General Public License v3.0 only
/// @{

#include "smartctl_ata_json_parser.h"
#include "json/json.hpp"
#include "hz/debug.h"
#include "hz/string_algo.h"
#include "smartctl_version_parser.h"
#include "hz/string_num.h"



/*
Information not printed in JSON yet:
Expand Down Expand Up @@ -48,7 +55,7 @@ Information not printed in JSON yet:
_text_only/power_mode
- Automatic Offline Data Collection toggle support
text_only/aodc_support
_text_only/aodc_support
- Directory log supported
We don't use this.
Expand All @@ -68,11 +75,131 @@ _custom/smart_enabled
*/


namespace {


/// Get json node data. The path is slash-separated string.
/// \throws std::runtime_error If not found or one of the paths is not an object
template<typename T>
T get_node_data(const nlohmann::json& root, const std::string& path)
{
using namespace std::literals;

std::vector<std::string> components;
hz::string_split(path, '/', components, true);

const auto* curr = &root;
for (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {
const std::string& comp_name = components[comp_index];

if (!curr->is_object()) { // we can't have non-object values in the middle of a path
throw std::runtime_error("Cannot get node data \""s + path + "\", component \"" + comp_name + "\" is not an object.");
}
if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists
const auto& jval = iter.value();
if (comp_index + 1 == components.size()) { // it's the "value" component
try {
return jval.get<T>(); // may throw json::type_error
}
catch (nlohmann::json::type_error& ex) {
throw std::runtime_error(ex.what());
}
}
// continue to the next component
curr = &jval;

} else { // path component doesn't exist
throw std::runtime_error("Cannot get node data \""s + path + "\", component \"" + comp_name + "\" does not exist.");
}
}

throw std::runtime_error("Cannot get node data \""s + path + "\": Internal error.");
}


/// Get json node data. The path is slash-separated string.
/// If an error is found, the default value is returned.
template<typename T>
T get_node_data(const nlohmann::json& root, const std::string& path, const T& default_value)
{
try {
return get_node_data<T>(root, path);
}
catch (std::runtime_error& ex) {
return default_value;
}
}


}



bool SmartctlAtaJsonParser::parse_full(const std::string& full)
bool SmartctlAtaJsonParser::parse_full(const std::string& json_data_full)
{
// TODO
return false;
this->set_data_full(json_data_full);

if (hz::string_trim_copy(json_data_full).empty()) {
set_error_msg("Smartctl data is empty.");
debug_out_warn("app", DBG_FUNC_MSG << "Empty string passed as an argument. Returning.\n");
return false;
}

try {
const nlohmann::json root_node = nlohmann::json::parse(json_data_full);

{
AtaStorageProperty p;
p.set_name("Smartctl version", "smartctl/version/_merged", "Smartctl Version");
auto json_ver = get_node_data<std::vector<int>>(root_node, "smartctl/version", {});
if (json_ver.size() >= 2) {
p.reported_value = hz::number_to_string_nolocale(json_ver.at(0)) + "." + hz::number_to_string_nolocale(json_ver.at(1));
}
p.value = p.reported_value; // string-type value
p.section = AtaStorageProperty::Section::info; // add to info section
add_property(p);
}
// {
// AtaStorageProperty p;
// p.set_name("Smartctl version", "smartctl/version/_merged_full", "Smartctl Version");
// p.reported_value = version_full;
// p.value = p.reported_value; // string-type value
// p.section = AtaStorageProperty::Section::info; // add to info section
// add_property(p);
// }

// if (!SmartctlVersionParser::check_parsed_version(SmartctlParserType::Text, version)) {
// set_error_msg("Incompatible smartctl version.");
// debug_out_warn("app", DBG_FUNC_MSG << "Incompatible smartctl version. Returning.\n");
// return false;
// }

const std::unordered_map<std::string, std::string> info_keys = {
{"model_family", _("Model Family")},
};

for (const auto& [key, jval] : root_node.items()) {
if (auto found = info_keys.find(key); found != info_keys.end()) {
AtaStorageProperty p;
p.section = AtaStorageProperty::Section::info;
p.set_name(key, key, found->second);
p.reported_value = jval.get<std::string>();
p.value = p.reported_value; // string-type value

// parse_section_info_property(p); // set type and the typed value. may change generic_name too.

add_property(p);
}
}


}
catch (const nlohmann::json::parse_error& e) {
debug_out_warn("app", DBG_FUNC_MSG << "Error parsing smartctl output as JSON. Returning.\n");
return false;
}

return true;
}


Expand Down
2 changes: 1 addition & 1 deletion src/applib/smartctl_ata_json_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class SmartctlAtaJsonParser : public SmartctlParser {
SmartctlAtaJsonParser() = default;

// Overridden
bool parse_full(const std::string& full) override;
bool parse_full(const std::string& json_data_full) override;


};
Expand Down
38 changes: 34 additions & 4 deletions src/applib/storage_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,26 @@ std::string StorageDevice::fetch_data_and_parse(const std::shared_ptr<CommandExe
std::string output;
std::string error_msg;

const SmartctlParserSettingType default_parser_type = SmartctlParserSettingType::Text;

// instead of -x, we use all the individual options -x encompasses, so that
// an addition to default -x output won't affect us.
if (this->get_type_argument() == "scsi") { // not sure about correctness... FIXME probably fails with RAID/scsi
// This doesn't do much yet, but just in case...
// SCSI equivalent of -x:
error_msg = execute_device_smartctl("--health --info --attributes --log=error --log=selftest --log=background --log=sasphy", smartctl_ex, output);

} else {
// ATA equivalent of -x:
error_msg = execute_device_smartctl("--health --info --get=all --capabilities --attributes --format=brief --log=xerror,50,error --log=xselftest,50,selftest --log=selective --log=directory --log=scttemp --log=scterc --log=devstat --log=sataphy",
smartctl_ex, output, true); // set type to invalid if needed
// ATA equivalent of -x.
std::string command_options = "--health --info --get=all --capabilities --attributes --format=brief --log=xerror,50,error --log=xselftest,50,selftest --log=selective --log=directory --log=scttemp --log=scterc --log=devstat --log=sataphy";
if (default_parser_type == SmartctlParserSettingType::Json) {
// --json flags: o means include original output (just in case).
command_options += " --json=o";
}

error_msg = execute_device_smartctl(command_options, smartctl_ex, output, true); // set type to invalid if needed
}

// See notes above (in fetch_basic_data_and_parse()).
if (get_detected_type() == DetectedType::invalid && get_type_argument().empty()) {
debug_out_info("app", "The device seems to be of different type than auto-detected, trying again with scsi.\n");
Expand Down Expand Up @@ -308,7 +317,28 @@ std::string StorageDevice::parse_data()
disk_type = hdd_.value() ? AtaStorageAttribute::DiskType::Hdd : AtaStorageAttribute::DiskType::Ssd;
}

auto parser = SmartctlParser::create(SmartctlParserType::Text);
std::string error_msg;
auto parser_type = leaf::try_handle_some(
[this]() -> leaf::result<SmartctlParserType>
{
return SmartctlParser::detect_output_type(this->full_output_);
},
[&error_msg](leaf::match<SmartctlParserError, SmartctlParserError::EmptyInput>) -> SmartctlParserType
{
error_msg = "Empty input while trying to detect smartctl output format.";
return SmartctlParserType::Text;
},
[&error_msg](leaf::match<SmartctlParserError, SmartctlParserError::UnsupportedFormat>) -> SmartctlParserType
{
error_msg = "Unsupported format while trying to detect smartctl output format.";
return SmartctlParserType::Text;
}
);
if (!error_msg.empty()) {
return error_msg;
}

auto parser = SmartctlParser::create(parser_type.value());
DBG_ASSERT_RETURN(parser, "Cannot create parser");

if (parser->parse_full(this->full_output_)) { // try to parse it (parse only, set the properties after basic parsing).
Expand Down

0 comments on commit 7f688e0

Please sign in to comment.