Add statistics and make scsictl accept generic key/value parameters (#1237/#1238) (#1262)

* Add statistics and make scsictl accept generic key/value parameters
This commit is contained in:
Uwe Seimet 2023-10-30 13:32:45 +01:00 committed by GitHub
parent 8f45e4f491
commit b7cb23e391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 557 additions and 105 deletions

View File

@ -273,6 +273,9 @@ bool Disk::Eject(bool force)
// The image file for this drive is not in use anymore
UnreserveFile();
sector_read_count = 0;
sector_write_count = 0;
}
return status;
@ -506,6 +509,8 @@ int Disk::Read(span<uint8_t> buf, uint64_t block)
throw scsi_exception(sense_key::medium_error, asc::read_fault);
}
++sector_read_count;
return GetSectorSizeInBytes();
}
@ -518,6 +523,8 @@ void Disk::Write(span<const uint8_t> buf, uint64_t block)
if (!cache->WriteSector(buf, static_cast<uint32_t>(block))) {
throw scsi_exception(sense_key::medium_error, asc::write_fault);
}
++sector_write_count;
}
void Disk::Seek()
@ -711,3 +718,35 @@ bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t
return true;
}
vector<PbStatistics> Disk::GetStatistics() const
{
vector<PbStatistics> statistics = PrimaryDevice::GetStatistics();
// Enrich cache statistics with device information before adding them to device statistics
if (cache) {
for (auto& s : cache->GetStatistics(IsReadOnly())) {
s.set_id(GetId());
s.set_unit(GetLun());
statistics.push_back(s);
}
}
PbStatistics s;
s.set_id(GetId());
s.set_unit(GetLun());
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(SECTOR_READ_COUNT);
s.set_value(sector_read_count);
statistics.push_back(s);
if (!IsReadOnly()) {
s.set_key(SECTOR_WRITE_COUNT);
s.set_value(sector_write_count);
statistics.push_back(s);
}
return statistics;
}

View File

@ -8,7 +8,6 @@
// XM6i
// Copyright (C) 2010-2015 isaki@NetBSD.org
//
// Imported sava's Anex86/T98Next image and MO format support patch.
// Comments translated to english by akuker.
//
//---------------------------------------------------------------------------
@ -16,6 +15,7 @@
#pragma once
#include "shared/scsi.h"
#include "shared/piscsi_util.h"
#include "device_factory.h"
#include "disk_track.h"
#include "disk_cache.h"
@ -42,6 +42,12 @@ class Disk : public StorageDevice, private ScsiBlockCommands
// Sector size shift count (9=512, 10=1024, 11=2048, 12=4096)
uint32_t size_shift_count = 0;
uint64_t sector_read_count = 0;
uint64_t sector_write_count = 0;
inline static const string SECTOR_READ_COUNT = "sector_read_count";
inline static const string SECTOR_WRITE_COUNT = "sector_write_count";
public:
using StorageDevice::StorageDevice;
@ -62,6 +68,8 @@ public:
bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t);
void FlushCache() override;
vector<PbStatistics> GetStatistics() const override;
private:
// Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm)

View File

@ -27,11 +27,11 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff
assert(imgoff >= 0);
}
bool DiskCache::Save() const
bool DiskCache::Save()
{
// Save valid tracks
return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c)
{ return c.disktrk != nullptr && !c.disktrk->Save(sec_path); });
{ return c.disktrk != nullptr && !c.disktrk->Save(sec_path, cache_miss_write_count); });
}
shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
@ -120,7 +120,7 @@ shared_ptr<DiskTrack> DiskCache::Assign(int track)
}
// Save this track
if (!cache[c].disktrk->Save(sec_path)) {
if (!cache[c].disktrk->Save(sec_path, cache_miss_write_count)) {
return nullptr;
}
@ -156,17 +156,16 @@ bool DiskCache::Load(int index, int track, shared_ptr<DiskTrack> disktrk)
sectors = 0x100;
}
// Create a disk track
if (disktrk == nullptr) {
disktrk = make_shared<DiskTrack>();
}
// Initialize disk track
disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset);
// Try loading
if (!disktrk->Load(sec_path)) {
// Failure
if (!disktrk->Load(sec_path, cache_miss_read_count)) {
++read_error_count;
return false;
}
@ -190,3 +189,35 @@ void DiskCache::UpdateSerialNumber()
}
}
vector<PbStatistics> DiskCache::GetStatistics(bool is_read_only) const
{
vector<PbStatistics> statistics;
PbStatistics s;
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(CACHE_MISS_READ_COUNT);
s.set_value(cache_miss_read_count);
statistics.push_back(s);
if (!is_read_only) {
s.set_key(CACHE_MISS_WRITE_COUNT);
s.set_value(cache_miss_write_count);
statistics.push_back(s);
}
s.set_category(PbStatisticsCategory::CATEGORY_ERROR);
s.set_key(READ_ERROR_COUNT);
s.set_value(read_error_count);
statistics.push_back(s);
if (!is_read_only) {
s.set_key(WRITE_ERROR_COUNT);
s.set_value(write_error_count);
statistics.push_back(s);
}
return statistics;
}

View File

@ -15,18 +15,30 @@
#pragma once
#include "generated/piscsi_interface.pb.h"
#include <span>
#include <array>
#include <memory>
#include <string>
using namespace std;
using namespace piscsi_interface;
class DiskCache
{
// Number of tracks to cache
static const int CACHE_MAX = 16;
uint64_t read_error_count = 0;
uint64_t write_error_count = 0;
uint64_t cache_miss_read_count = 0;
uint64_t cache_miss_write_count = 0;
inline static const string READ_ERROR_COUNT = "read_error_count";
inline static const string WRITE_ERROR_COUNT = "write_error_count";
inline static const string CACHE_MISS_READ_COUNT = "cache_miss_read_count";
inline static const string CACHE_MISS_WRITE_COUNT = "cache_miss_write_count";
public:
// Internal data definition
@ -40,11 +52,12 @@ public:
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting
// Access
bool Save() const; // Save and release all
bool Save(); // Save and release all
bool ReadSector(span<uint8_t>, uint32_t); // Sector Read
bool WriteSector(span<const uint8_t>, uint32_t); // Sector Write
vector<PbStatistics> GetStatistics(bool) const;
private:
// Internal Management

View File

@ -48,7 +48,7 @@ void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff)
dt.imgoffset = imgoff;
}
bool DiskTrack::Load(const string& path)
bool DiskTrack::Load(const string& path, uint64_t& cache_miss_read_count)
{
// Not needed if already loaded
if (dt.init) {
@ -56,6 +56,8 @@ bool DiskTrack::Load(const string& path)
return true;
}
++cache_miss_read_count;
// Calculate offset (previous tracks are considered to hold 256 sectors)
off_t offset = ((off_t)dt.track << 8);
if (dt.raw) {
@ -138,7 +140,7 @@ bool DiskTrack::Load(const string& path)
return true;
}
bool DiskTrack::Save(const string& path)
bool DiskTrack::Save(const string& path, uint64_t& cache_miss_write_count)
{
// Not needed if not initialized
if (!dt.init) {
@ -150,6 +152,8 @@ bool DiskTrack::Save(const string& path)
return true;
}
++cache_miss_write_count;
// Need to write
assert(dt.buffer);
assert((dt.sectors > 0) && (dt.sectors <= 0x100));

View File

@ -50,10 +50,9 @@ private:
friend class DiskCache;
void Init(int track, int size, int sectors, bool raw = false, off_t imgoff = 0);
bool Load(const string& path);
bool Save(const string& path);
bool Load(const string& path, uint64_t&);
bool Save(const string& path, uint64_t&);
// Read / Write
bool ReadSector(span<uint8_t>, int) const; // Sector Read
bool WriteSector(span<const uint8_t> buf, int); // Sector Write

View File

@ -54,7 +54,13 @@ public:
void Reset() override;
virtual void FlushCache() {
// Devices with a cache have to implement this method
// Devices with a cache have to override this method
}
virtual vector<PbStatistics> GetStatistics() const {
// Devices which provide statistics have to override this method
return vector<PbStatistics>();
}
protected:

View File

@ -119,7 +119,7 @@ vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
// - The SCSI/Link apparently has about 6KB buffer space for packets.
//
//---------------------------------------------------------------------------
int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t) const
int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t)
{
int rx_packet_size = 0;
const auto response = (scsi_resp_read_t*)buf.data();
@ -155,6 +155,8 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t) const
return DAYNAPORT_READ_HEADER_SZ;
}
byte_read_count += rx_packet_size * read_count;
LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count));
// This is a very basic filter to prevent unnecessary packets from
@ -252,17 +254,19 @@ int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t) const
// XX XX ... is the actual packet
//
//---------------------------------------------------------------------------
bool SCSIDaynaPort::Write(cdb_t cdb, span<const uint8_t> buf) const
bool SCSIDaynaPort::Write(cdb_t cdb, span<const uint8_t> buf)
{
if (const int data_format = cdb[5]; data_format == 0x00) {
const int data_length = GetInt16(cdb, 3);
tap.Send(buf.data(), data_length);
byte_write_count += data_length;
LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
}
else if (data_format == 0x80) {
// The data length is specified in the first 2 bytes of the payload
const int data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
tap.Send(&(buf.data()[4]), data_length);
byte_write_count += data_length;
LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
}
else {
@ -305,7 +309,7 @@ void SCSIDaynaPort::TestUnitReady()
EnterStatusPhase();
}
void SCSIDaynaPort::Read6() const
void SCSIDaynaPort::Read6()
{
// Get record number and block number
const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff;
@ -478,3 +482,23 @@ void SCSIDaynaPort::EnableInterface() const
EnterStatusPhase();
}
vector<PbStatistics> SCSIDaynaPort::GetStatistics() const
{
vector<PbStatistics> statistics = PrimaryDevice::GetStatistics();
PbStatistics s;
s.set_id(GetId());
s.set_unit(GetLun());
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(BYTE_READ_COUNT);
s.set_value(byte_read_count);
statistics.push_back(s);
s.set_key(BYTE_WRITE_COUNT);
s.set_value(byte_write_count);
statistics.push_back(s);
return statistics;
}

View File

@ -44,6 +44,12 @@
//===========================================================================
class SCSIDaynaPort : public PrimaryDevice
{
uint64_t byte_read_count = 0;
uint64_t byte_write_count = 0;
inline static const string BYTE_READ_COUNT = "byte_read_count";
inline static const string BYTE_WRITE_COUNT = "byte_write_count";
public:
explicit SCSIDaynaPort(int);
@ -56,19 +62,21 @@ public:
// Commands
vector<uint8_t> InquiryInternal() const override;
int Read(cdb_t, vector<uint8_t>&, uint64_t) const;
bool Write(cdb_t, span<const uint8_t>) const;
int Read(cdb_t, vector<uint8_t>&, uint64_t);
bool Write(cdb_t, span<const uint8_t>);
int RetrieveStats(cdb_t, vector<uint8_t>&) const;
void TestUnitReady() override;
void Read6() const;
void Read6();
void Write6() const;
void RetrieveStatistics() const;
void SetInterfaceMode() const;
void SetMcastAddr() const;
void EnableInterface() const;
vector<PbStatistics> GetStatistics() const override;
static const int DAYNAPORT_BUFFER_SIZE = 0x1000000;
static const int CMD_SCSILINK_STATS = 0x09;

View File

@ -115,6 +115,8 @@ void SCSIPrinter::Print()
LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
" bytes, " + to_string(length) + " bytes expected");
++print_error_count;
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
@ -129,6 +131,8 @@ void SCSIPrinter::SynchronizeBuffer()
if (!out.is_open()) {
LogWarn("Nothing to print");
++print_warning_count;
throw scsi_exception(sense_key::aborted_command);
}
@ -145,6 +149,8 @@ void SCSIPrinter::SynchronizeBuffer()
if (system(cmd.c_str())) {
LogError("Printing file '" + filename + "' failed, the printing system might not be configured");
++print_error_count;
CleanUp();
throw scsi_exception(sense_key::aborted_command);
@ -157,6 +163,8 @@ void SCSIPrinter::SynchronizeBuffer()
bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
{
byte_receive_count += buf.size();
if (!out.is_open()) {
vector<char> f(file_template.begin(), file_template.end());
f.push_back(0);
@ -165,6 +173,9 @@ bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
const int fd = mkstemp(f.data());
if (fd == -1) {
LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
++print_error_count;
return false;
}
close(fd);
@ -173,6 +184,8 @@ bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
out.open(filename, ios::binary);
if (out.fail()) {
++print_error_count;
throw scsi_exception(sense_key::aborted_command);
}
@ -183,5 +196,43 @@ bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
out.write((const char *)buf.data(), buf.size());
return !out.fail();
const bool status = out.fail();
if (!status) {
++print_error_count;
}
return !status;
}
vector<PbStatistics> SCSIPrinter::GetStatistics() const
{
vector<PbStatistics> statistics = PrimaryDevice::GetStatistics();
PbStatistics s;
s.set_id(GetId());
s.set_unit(GetLun());
s.set_category(PbStatisticsCategory::CATEGORY_INFO);
s.set_key(FILE_PRINT_COUNT);
s.set_value(file_print_count);
statistics.push_back(s);
s.set_key(BYTE_RECEIVE_COUNT);
s.set_value(byte_receive_count);
statistics.push_back(s);
s.set_category(PbStatisticsCategory::CATEGORY_ERROR);
s.set_key(PRINT_ERROR_COUNT);
s.set_value(print_error_count);
statistics.push_back(s);
s.set_category(PbStatisticsCategory::CATEGORY_WARNING);
s.set_key(PRINT_WARNING_COUNT);
s.set_value(print_warning_count);
statistics.push_back(s);
return statistics;
}

View File

@ -21,10 +21,20 @@ using namespace std;
class SCSIPrinter : public PrimaryDevice, private ScsiPrinterCommands
{
uint64_t file_print_count = 0;
uint64_t byte_receive_count = 0;
uint64_t print_error_count = 0;
uint64_t print_warning_count = 0;
static const int NOT_RESERVED = -2;
static constexpr const char *PRINTER_FILE_PATTERN = "/piscsi_sclp-XXXXXX";
inline static const string FILE_PRINT_COUNT = "file_print_count";
inline static const string BYTE_RECEIVE_COUNT = "byte_receive_count";
inline static const string PRINT_ERROR_COUNT = "print_error_count";
inline static const string PRINT_WARNING_COUNT = "print_warning_count";
public:
explicit SCSIPrinter(int);
@ -39,6 +49,8 @@ public:
bool WriteByteSequence(span<const uint8_t>) override;
vector<PbStatistics> GetStatistics() const override;
private:
void TestUnitReady() override;

View File

@ -357,9 +357,8 @@ bool Piscsi::ExecuteCommand(CommandContext& context)
break;
case SERVER_INFO:
response.GetServerInfo(*result.mutable_server_info(), controller_manager.GetAllDevices(),
executor->GetReservedIds(), piscsi_image.GetDefaultFolder(),
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
response.GetServerInfo(*result.mutable_server_info(), command, controller_manager.GetAllDevices(),
executor->GetReservedIds(), piscsi_image.GetDefaultFolder(), piscsi_image.GetDepth());
context.WriteSuccessResult(result);
break;
@ -408,6 +407,11 @@ bool Piscsi::ExecuteCommand(CommandContext& context)
context.WriteSuccessResult(result);
break;
case STATISTICS_INFO:
response.GetStatisticsInfo(*result.mutable_statistics_info(), controller_manager.GetAllDevices());
context.WriteSuccessResult(result);
break;
case OPERATION_INFO:
response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth());
context.WriteSuccessResult(result);

View File

@ -227,19 +227,62 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
result.set_status(true);
}
void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const unordered_set<shared_ptr<PrimaryDevice>>& devices,
const unordered_set<int>& reserved_ids, const string& default_folder, const string& folder_pattern,
const string& file_pattern, int scan_depth) const
void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const PbCommand& command,
const unordered_set<shared_ptr<PrimaryDevice>>& devices, const unordered_set<int>& reserved_ids,
const string& default_folder, int scan_depth) const
{
GetVersionInfo(*server_info.mutable_version_info());
GetLogLevelInfo(*server_info.mutable_log_level_info());
GetDeviceTypesInfo(*server_info.mutable_device_types_info());
GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth);
GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info());
GetMappingInfo(*server_info.mutable_mapping_info());
GetDevices(devices, server_info, default_folder);
GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids);
GetOperationInfo(*server_info.mutable_operation_info(), scan_depth);
const vector<string> command_operations = Split(GetParam(command, "operations"), ',');
set<string, less<>> operations;
for (const string& operation : command_operations) {
string op;
ranges::transform(operation, back_inserter(op), ::toupper);
operations.insert(op);
}
if (!operations.empty()) {
spdlog::trace("Requested operation(s): " + Join(operations, ","));
}
if (HasOperation(operations, PbOperation::VERSION_INFO)) {
GetVersionInfo(*server_info.mutable_version_info());
}
if (HasOperation(operations, PbOperation::LOG_LEVEL_INFO)) {
GetLogLevelInfo(*server_info.mutable_log_level_info());
}
if (HasOperation(operations, PbOperation::DEVICE_TYPES_INFO)) {
GetDeviceTypesInfo(*server_info.mutable_device_types_info());
}
if (HasOperation(operations, PbOperation::DEFAULT_IMAGE_FILES_INFO)) {
GetAvailableImages(server_info, default_folder, GetParam(command, "folder_pattern"),
GetParam(command, "file_pattern"), scan_depth);
}
if (HasOperation(operations, PbOperation::NETWORK_INTERFACES_INFO)) {
GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info());
}
if (HasOperation(operations, PbOperation::MAPPING_INFO)) {
GetMappingInfo(*server_info.mutable_mapping_info());
}
if (HasOperation(operations, PbOperation::STATISTICS_INFO)) {
GetStatisticsInfo(*server_info.mutable_statistics_info(), devices);
}
if (HasOperation(operations, PbOperation::DEVICES_INFO)) {
GetDevices(devices, server_info, default_folder);
}
if (HasOperation(operations, PbOperation::RESERVED_IDS_INFO)) {
GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids);
}
if (HasOperation(operations, PbOperation::OPERATION_INFO)) {
GetOperationInfo(*server_info.mutable_operation_info(), scan_depth);
}
}
void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const
@ -272,6 +315,21 @@ void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const
}
}
void PiscsiResponse::GetStatisticsInfo(PbStatisticsInfo& statistics_info,
const unordered_set<shared_ptr<PrimaryDevice>>& devices) const
{
for (const auto& device : devices) {
for (const auto& statistics : device->GetStatistics()) {
auto s = statistics_info.add_statistics();
s->set_id(statistics.id());
s->set_unit(statistics.unit());
s->set_category(statistics.category());
s->set_key(statistics.key());
s->set_value(statistics.value());
}
}
}
void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const
{
auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required");
@ -324,6 +382,8 @@ void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth
CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types");
CreateOperation(operation_info, STATISTICS_INFO, "Get statistics");
CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs");
operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder");
@ -469,3 +529,8 @@ bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lowe
return true;
}
bool PiscsiResponse::HasOperation(const set<string, less<>>& operations, PbOperation operation)
{
return operations.empty() || operations.contains(PbOperation_Name(operation));
}

View File

@ -11,9 +11,11 @@
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "shared/piscsi_util.h"
#include "generated/piscsi_interface.pb.h"
#include <string>
#include <span>
#include <set>
using namespace std;
using namespace filesystem;
@ -33,17 +35,19 @@ public:
void GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&, const string&) const;
void GetDeviceTypesInfo(PbDeviceTypesInfo&) const;
void GetVersionInfo(PbVersionInfo&) const;
void GetServerInfo(PbServerInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&, const unordered_set<int>&,
const string&, const string&, const string&, int) const;
void GetServerInfo(PbServerInfo&, const PbCommand&, const unordered_set<shared_ptr<PrimaryDevice>>&,
const unordered_set<int>&, const string&, int) const;
void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const;
void GetMappingInfo(PbMappingInfo&) const;
void GetLogLevelInfo(PbLogLevelInfo&) const;
void GetStatisticsInfo(PbStatisticsInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&) const;
void GetOperationInfo(PbOperationInfo&, int) const;
private:
inline static const vector<string> EMPTY_VECTOR;
// TODO Try to get rid of this field by having the device instead of the factory providing the device data
const DeviceFactory device_factory;
void GetDeviceProperties(const Device&, PbDeviceProperties&) const;
@ -59,4 +63,6 @@ private:
static bool ValidateImageFile(const path&);
static bool FilterMatches(const string&, string_view);
static bool HasOperation(const set<string, less<>>&, PbOperation);
};

View File

@ -82,9 +82,12 @@ enum PbOperation {
// Make medium writable (not possible for read-only media)
UNPROTECT = 9;
// Gets the server information (PbServerInfo). Calling this operation should be avoided because it
// may return a lot of data. More specific other operations should be used instead.
// Gets the server information (PbServerInfo). Calling this operation without a list of operations should
// be avoided because this may return a lot of data. More specific other operations should be used instead.
// Parameters:
// "operations": Optional case insensitive comma-separated list of operation names to return data for,
// e.g. "version_info,log_level_info". Unknown operation names are ignored. If this parameter is missing
// the full set of data supported by PbServerInfo is returned.
// "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned
// "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned
SERVER_INFO = 10;
@ -190,6 +193,9 @@ enum PbOperation {
// Get operation meta data (PbOperationInfo)
OPERATION_INFO = 31;
// Get statistics (PbStatisticsInfo)
STATISTICS_INFO = 32;
}
// The operation parameter meta data. The parameter data type is provided by the protobuf API.
@ -286,7 +292,7 @@ message PbImageFile {
string name = 1;
// The assumed device type, based on the filename extension
PbDeviceType type = 2;
// The file size in bytes, 0 for block devices
// The file size in bytes, 0 for block devices in /dev
uint64 size = 3;
bool read_only = 4;
}
@ -311,6 +317,41 @@ message PbNetworkInterfacesInfo {
repeated string name = 1;
}
// Statistics categories ordered by increasing severity
enum PbStatisticsCategory {
CATEGORY_NONE = 0;
CATEGORY_INFO = 1;
CATEGORY_WARNING = 2;
CATEGORY_ERROR = 3;
}
message PbStatistics {
PbStatisticsCategory category = 1;
// The device ID and LUN for this statistics item. Both are -1 if the item is not device specific.
int32 id = 2;
int32 unit = 3;
// A symbolic unique item name, may be used for I18N. Supported values and their categories:
// "read_error_count" (ERROR, SCHD/SCRM/SCMO/SCCD)
// "write_error_count" (ERROR, SCHD/SCRM/SCMO)
// "cache_miss_read_count" (INFO, SCHD/SCRM/SCMO/SCCD)
// "cache_miss_write_count" (INFO, SCHD/SCRM/SCMO)
// "sector_read_count" (INFO, SCHD/SCRM/SCMO/SCCD)
// "sector_write_count" (INFO, SCHD/SCRM/SCMO)
// "byte_read_count" (INFO, SCDP)
// "byte_write_count" (INFO, SCDP)
// "print_error_count" (ERROR, SCLP)
// "print_warning_count" (WARNING, SCLP)
// "file_print_count" (INFO, SCLP)
// "byte_receive_count" (INFO, SCLP)
string key = 4;
uint64 value = 5;
}
// The information on collected statistics
message PbStatisticsInfo {
repeated PbStatistics statistics = 1;
}
// The device definition, sent from the client to the server
message PbDeviceDefinition {
int32 id = 1;
@ -407,6 +448,8 @@ message PbResult {
PbReservedIdsInfo reserved_ids_info = 12;
// The result of an OPERATION_INFO command
PbOperationInfo operation_info = 13;
// The result of a STATISTICS_INFO command
PbStatisticsInfo statistics_info = 15;
}
}
@ -430,4 +473,6 @@ message PbServerInfo {
PbDevicesInfo devices_info = 8;
// The operation meta data
PbOperationInfo operation_info = 9;
// The statistics
PbStatisticsInfo statistics_info = 10;
}

View File

@ -81,6 +81,9 @@ bool ScsictlCommands::Execute(string_view log_level, string_view default_folder,
case MAPPING_INFO:
return CommandMappingInfo();
case STATISTICS_INFO:
return CommandStatisticsInfo();
case OPERATION_INFO:
return CommandOperationInfo();
@ -245,16 +248,43 @@ bool ScsictlCommands::CommandServerInfo()
PbServerInfo server_info = result.server_info();
cout << scsictl_display.DisplayVersionInfo(server_info.version_info());
cout << scsictl_display.DisplayLogLevelInfo(server_info.log_level_info());
cout << scsictl_display.DisplayImageFilesInfo(server_info.image_files_info());
cout << scsictl_display.DisplayMappingInfo(server_info.mapping_info());
cout << scsictl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info());
cout << scsictl_display.DisplayDeviceTypesInfo(server_info.device_types_info());
cout << scsictl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info());
cout << scsictl_display.DisplayOperationInfo(server_info.operation_info());
if (server_info.has_version_info()) {
cout << scsictl_display.DisplayVersionInfo(server_info.version_info());
}
if (server_info.devices_info().devices_size()) {
if (server_info.has_log_level_info()) {
cout << scsictl_display.DisplayLogLevelInfo(server_info.log_level_info());
}
if (server_info.has_image_files_info()) {
cout << scsictl_display.DisplayImageFilesInfo(server_info.image_files_info());
}
if (server_info.has_mapping_info()) {
cout << scsictl_display.DisplayMappingInfo(server_info.mapping_info());
}
if (server_info.has_network_interfaces_info()) {
cout << scsictl_display.DisplayNetworkInterfaces(server_info.network_interfaces_info());
}
if (server_info.has_device_types_info()) {
cout << scsictl_display.DisplayDeviceTypesInfo(server_info.device_types_info());
}
if (server_info.has_reserved_ids_info()) {
cout << scsictl_display.DisplayReservedIdsInfo(server_info.reserved_ids_info());
}
if (server_info.has_statistics_info()) {
cout << scsictl_display.DisplayStatisticsInfo(server_info.statistics_info());
}
if (server_info.has_operation_info()) {
cout << scsictl_display.DisplayOperationInfo(server_info.operation_info());
}
if (server_info.has_devices_info() && server_info.devices_info().devices_size()) {
vector<PbDevice> sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
ranges::sort(sorted_devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); });
@ -326,6 +356,15 @@ bool ScsictlCommands::CommandMappingInfo()
return true;
}
bool ScsictlCommands::CommandStatisticsInfo()
{
SendCommand();
cout << scsictl_display.DisplayStatisticsInfo(result.statistics_info()) << flush;
return true;
}
bool ScsictlCommands::CommandOperationInfo()
{
SendCommand();

View File

@ -48,6 +48,7 @@ private:
bool CommandLogLevelInfo();
bool CommandReservedIdsInfo();
bool CommandMappingInfo();
bool CommandStatisticsInfo();
bool CommandOperationInfo();
bool SendCommand();
bool EvaluateParams(string_view, const string&, const string&);

View File

@ -35,7 +35,7 @@ void ScsiCtl::Banner(const vector<char *>& args) const
<< "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "
<< "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "
<< "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "
<< "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"
<< "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-S] [-v] [-V] [-y] [-X]\n"
<< " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "},"
<< " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n"
<< " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"
@ -82,7 +82,7 @@ int ScsiCtl::run(const vector<char *>& args) const
opterr = 1;
int opt;
while ((opt = getopt(static_cast<int>(args.size()), args.data(),
"e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) {
"e::lmos::vDINOSTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) {
switch (opt) {
case 'i':
if (const string error = SetIdAndLun(*device, optarg); !error.empty()) {
@ -130,7 +130,7 @@ int ScsiCtl::run(const vector<char *>& args) const
case 'e':
command.set_operation(DEFAULT_IMAGE_FILES_INFO);
if (optarg) {
SetPatternParams(command, optarg);
SetCommandParams(command, optarg);
}
break;
@ -208,10 +208,14 @@ int ScsiCtl::run(const vector<char *>& args) const
case 's':
command.set_operation(SERVER_INFO);
if (optarg) {
SetPatternParams(command, optarg);
SetCommandParams(command, optarg);
}
break;
case 'S':
command.set_operation(STATISTICS_INFO);
break;
case 'v':
cout << "scsictl version: " << piscsi_get_version_string() << '\n';
exit(EXIT_SUCCESS);

View File

@ -231,6 +231,38 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con
return s.str();
}
string ScsictlDisplay::DisplayStatisticsInfo(const PbStatisticsInfo& statistics_info) const
{
ostringstream s;
s << "Statistics:\n";
// Sort by ascending ID, LUN and key and by descending category
vector<PbStatistics> sorted_statistics = { statistics_info.statistics().begin(), statistics_info.statistics().end() };
ranges::sort(sorted_statistics, [] (const PbStatistics& a, const PbStatistics& b) {
if (a.category() > b.category()) return true;
if (a.category() < b.category()) return false;
if (a.id() < b.id()) return true;
if (a.id() > b.id()) return false;
if (a.unit() < b.unit()) return true;
if (a.unit() > b.unit()) return false;
return a.key() < b.key();
});
PbStatisticsCategory prev_category = PbStatisticsCategory::CATEGORY_NONE;
for (const auto& statistics : sorted_statistics) {
if (statistics.category() != prev_category) {
// Strip leading "CATEGORY_"
s << " " << PbStatisticsCategory_Name(statistics.category()).substr(9) << '\n';
prev_category = statistics.category();
}
s << " " << statistics.id() << ":" << statistics.unit() << " " << statistics.key() << ": " << statistics.value() << '\n';
}
return s.str();
}
string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_info) const
{
ostringstream s;

View File

@ -32,6 +32,7 @@ public:
string DisplayImageFilesInfo(const PbImageFilesInfo&) const;
string DisplayNetworkInterfaces(const PbNetworkInterfacesInfo&) const;
string DisplayMappingInfo(const PbMappingInfo&) const;
string DisplayStatisticsInfo(const PbStatisticsInfo&) const;
string DisplayOperationInfo(const PbOperationInfo&) const;
private:

View File

@ -42,21 +42,33 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa
}
}
void protobuf_util::SetPatternParams(PbCommand& command, const string& patterns)
void protobuf_util::SetCommandParams(PbCommand& command, const string& params)
{
string folder_pattern;
string file_pattern;
string operations;
if (const auto& components = Split(patterns, ':', 2); components.size() == 2) {
folder_pattern = components[0];
file_pattern = components[1];
}
else {
file_pattern = patterns;
switch (const auto& components = Split(params, COMPONENT_SEPARATOR, 3); components.size()) {
case 3:
operations = components[2];
[[fallthrough]];
case 2:
folder_pattern = components[0];
file_pattern = components[1];
break;
case 1:
file_pattern = components[0];
break;
default:
break;
}
SetParam(command, "folder_pattern", folder_pattern);
SetParam(command, "file_pattern", file_pattern);
SetParam(command, "operations", operations);
}
void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data)

View File

@ -38,7 +38,7 @@ namespace protobuf_util
}
void ParseParameters(PbDeviceDefinition&, const string&);
void SetPatternParams(PbCommand&, const string&);
void SetCommandParams(PbCommand&, const string&);
void SetProductData(PbDeviceDefinition&, const string&);
string SetIdAndLun(PbDeviceDefinition&, const string&);
string ListDevices(const vector<PbDevice>&);

View File

@ -9,6 +9,7 @@
#include "mocks.h"
#include "shared/piscsi_version.h"
#include "shared/protobuf_util.h"
#include "controllers/controller_manager.h"
#include "devices/device_factory.h"
#include "generated/piscsi_interface.pb.h"
@ -16,6 +17,7 @@
#include <sys/stat.h>
using namespace piscsi_interface;
using namespace protobuf_util;
TEST(PiscsiResponseTest, Operation_Count)
{
@ -178,15 +180,41 @@ TEST(PiscsiResponseTest, GetServerInfo)
const unordered_set<shared_ptr<PrimaryDevice>> devices;
const unordered_set<int> ids = { 1, 3 };
PbServerInfo info;
response.GetServerInfo(info, devices, ids, "default_folder", "", "", 1234);
EXPECT_EQ(piscsi_major_version, info.version_info().major_version());
EXPECT_EQ(piscsi_minor_version, info.version_info().minor_version());
EXPECT_EQ(piscsi_patch_version, info.version_info().patch_version());
EXPECT_EQ(level::level_string_views[get_level()], info.log_level_info().current_log_level());
EXPECT_EQ("default_folder", info.image_files_info().default_image_folder());
EXPECT_EQ(1234, info.image_files_info().depth());
EXPECT_EQ(2, info.reserved_ids_info().ids().size());
PbCommand command;
PbServerInfo info1;
response.GetServerInfo(info1, command, devices, ids, "default_folder", 1234);
EXPECT_TRUE(info1.has_version_info());
EXPECT_TRUE(info1.has_log_level_info());
EXPECT_TRUE(info1.has_device_types_info());
EXPECT_TRUE(info1.has_image_files_info());
EXPECT_TRUE(info1.has_network_interfaces_info());
EXPECT_TRUE(info1.has_mapping_info());
EXPECT_TRUE(info1.has_statistics_info());
EXPECT_FALSE(info1.has_devices_info());
EXPECT_TRUE(info1.has_reserved_ids_info());
EXPECT_TRUE(info1.has_operation_info());
EXPECT_EQ(piscsi_major_version, info1.version_info().major_version());
EXPECT_EQ(piscsi_minor_version, info1.version_info().minor_version());
EXPECT_EQ(piscsi_patch_version, info1.version_info().patch_version());
EXPECT_EQ(level::level_string_views[get_level()], info1.log_level_info().current_log_level());
EXPECT_EQ("default_folder", info1.image_files_info().default_image_folder());
EXPECT_EQ(1234, info1.image_files_info().depth());
EXPECT_EQ(2, info1.reserved_ids_info().ids().size());
SetParam(command, "operations", "log_level_info,mapping_info");
PbServerInfo info2;
response.GetServerInfo(info2, command, devices, ids, "default_folder", 1234);
EXPECT_FALSE(info2.has_version_info());
EXPECT_TRUE(info2.has_log_level_info());
EXPECT_FALSE(info2.has_device_types_info());
EXPECT_FALSE(info2.has_image_files_info());
EXPECT_FALSE(info2.has_network_interfaces_info());
EXPECT_TRUE(info2.has_mapping_info());
EXPECT_FALSE(info2.has_statistics_info());
EXPECT_FALSE(info2.has_devices_info());
EXPECT_FALSE(info2.has_reserved_ids_info());
EXPECT_FALSE(info2.has_operation_info());
}
TEST(PiscsiResponseTest, GetVersionInfo)

View File

@ -53,27 +53,38 @@ TEST(ProtobufUtil, ParseParameters)
TestSpecialDevice("services");
}
TEST(ProtobufUtil, SetPatternParams)
TEST(ProtobufUtil, SetCommandParams)
{
PbCommand command1;
SetPatternParams(command1, "file");
SetCommandParams(command1, "file");
EXPECT_EQ("", GetParam(command1, "folder_pattern"));
EXPECT_EQ("file", GetParam(command1, "file_pattern"));
PbCommand command2;
SetPatternParams(command2, ":file");
SetCommandParams(command2, ":file");
EXPECT_EQ("", GetParam(command2, "folder_pattern"));
EXPECT_EQ("file", GetParam(command2, "file_pattern"));
PbCommand command3;
SetPatternParams(command3, "folder:");
EXPECT_EQ("folder", GetParam(command3, "folder_pattern"));
EXPECT_EQ("", GetParam(command3, "file_pattern"));
SetCommandParams(command3, "file:");
EXPECT_EQ("file", GetParam(command3, "file_pattern"));
EXPECT_EQ("", GetParam(command3, "folder_pattern"));
PbCommand command4;
SetPatternParams(command4, "folder:file");
SetCommandParams(command4, "folder:file");
EXPECT_EQ("folder", GetParam(command4, "folder_pattern"));
EXPECT_EQ("file", GetParam(command4, "file_pattern"));
PbCommand command5;
SetCommandParams(command5, "folder:file:");
EXPECT_EQ("folder", GetParam(command5, "folder_pattern"));
EXPECT_EQ("file", GetParam(command5, "file_pattern"));
PbCommand command6;
SetCommandParams(command6, "folder:file:operations");
EXPECT_EQ("folder", GetParam(command6, "folder_pattern"));
EXPECT_EQ("file", GetParam(command6, "file_pattern"));
EXPECT_EQ("operations", GetParam(command6, "operations"));
}
TEST(ProtobufUtil, ListDevices)

View File

<
@ -7,30 +7,31 @@ scsictl \- Sends management commands to the piscsi process
\fB\-l\fR |
\fB\-m\fR |
\fB\-o\fR |
\fB\-s\fR |
\fB\-v\fR |
\fB\-D\fR |
\fB\-I\fR |
\fB\-L\fR |
\fB\-O\fR |
\fB\-P\fR |
\fB\-S\fR |
\fB\-T\fR |
\fB\-V\fR |
\fB\-X\fR |
[\fB\-C\fR \fIFILENAME:FILESIZE\fR]
[\fB\-E\fR \fIFILENAME\fR]
[\fB\-F\fR \fIIMAGE_FOLDER\fR]
[\fB\-R\fR \fICURRENT_NAME:NEW_NAME\fR]
[\fB\-c\fR \fICMD\fR]
[\fB\-f\fR \fIFILE|PARAM\fR]
[\fB\-g\fR \fILOG_LEVEL\fR]
[\fB\-h\fR \fIHOST\fR]
[\fB\-i\fR \fIID[:LUN]\fR