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 // The image file for this drive is not in use anymore
UnreserveFile(); UnreserveFile();
sector_read_count = 0;
sector_write_count = 0;
} }
return status; 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); throw scsi_exception(sense_key::medium_error, asc::read_fault);
} }
++sector_read_count;
return GetSectorSizeInBytes(); 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))) { if (!cache->WriteSector(buf, static_cast<uint32_t>(block))) {
throw scsi_exception(sense_key::medium_error, asc::write_fault); throw scsi_exception(sense_key::medium_error, asc::write_fault);
} }
++sector_write_count;
} }
void Disk::Seek() void Disk::Seek()
@ -711,3 +718,35 @@ bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t
return true; 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 // XM6i
// Copyright (C) 2010-2015 isaki@NetBSD.org // Copyright (C) 2010-2015 isaki@NetBSD.org
// //
// Imported sava's Anex86/T98Next image and MO format support patch.
// Comments translated to english by akuker. // Comments translated to english by akuker.
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -16,6 +15,7 @@
#pragma once #pragma once
#include "shared/scsi.h" #include "shared/scsi.h"
#include "shared/piscsi_util.h"
#include "device_factory.h" #include "device_factory.h"
#include "disk_track.h" #include "disk_track.h"
#include "disk_cache.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) // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096)
uint32_t size_shift_count = 0; 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: public:
using StorageDevice::StorageDevice; using StorageDevice::StorageDevice;
@ -62,6 +68,8 @@ public:
bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t); bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t);
void FlushCache() override; void FlushCache() override;
vector<PbStatistics> GetStatistics() const override;
private: private:
// Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm) // 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); assert(imgoff >= 0);
} }
bool DiskCache::Save() const bool DiskCache::Save()
{ {
// Save valid tracks // Save valid tracks
return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c) 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) shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
@ -120,7 +120,7 @@ shared_ptr<DiskTrack> DiskCache::Assign(int track)
} }
// Save this track // Save this track
if (!cache[c].disktrk->Save(sec_path)) { if (!cache[c].disktrk->Save(sec_path, cache_miss_write_count)) {
return nullptr; return nullptr;
} }
@ -156,17 +156,16 @@ bool DiskCache::Load(int index, int track, shared_ptr<DiskTrack> disktrk)
sectors = 0x100; sectors = 0x100;
} }
// Create a disk track
if (disktrk == nullptr) { if (disktrk == nullptr) {
disktrk = make_shared<DiskTrack>(); disktrk = make_shared<DiskTrack>();
} }
// Initialize disk track
disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset); disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset);
// Try loading // Try loading
if (!disktrk->Load(sec_path)) { if (!disktrk->Load(sec_path, cache_miss_read_count)) {
// Failure ++read_error_count;
return false; 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 #pragma once
#include "generated/piscsi_interface.pb.h"
#include <span> #include <span>
#include <array> #include <array>
#include <memory> #include <memory>
#include <string> #include <string>
using namespace std; using namespace std;
using namespace piscsi_interface;
class DiskCache class DiskCache
{ {
// Number of tracks to cache // Number of tracks to cache
static const int CACHE_MAX = 16; 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: public:
// Internal data definition // Internal data definition
@ -40,11 +52,12 @@ public:
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting
// Access bool Save(); // Save and release all
bool Save() const; // Save and release all
bool ReadSector(span<uint8_t>, uint32_t); // Sector Read bool ReadSector(span<uint8_t>, uint32_t); // Sector Read
bool WriteSector(span<const uint8_t>, uint32_t); // Sector Write bool WriteSector(span<const uint8_t>, uint32_t); // Sector Write
vector<PbStatistics> GetStatistics(bool) const;
private: private:
// Internal Management // 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; 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 // Not needed if already loaded
if (dt.init) { if (dt.init) {
@ -56,6 +56,8 @@ bool DiskTrack::Load(const string& path)
return true; return true;
} }
++cache_miss_read_count;
// Calculate offset (previous tracks are considered to hold 256 sectors) // Calculate offset (previous tracks are considered to hold 256 sectors)
off_t offset = ((off_t)dt.track << 8); off_t offset = ((off_t)dt.track << 8);
if (dt.raw) { if (dt.raw) {
@ -138,7 +140,7 @@ bool DiskTrack::Load(const string& path)
return true; 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 // Not needed if not initialized
if (!dt.init) { if (!dt.init) {
@ -150,6 +152,8 @@ bool DiskTrack::Save(const string& path)
return true; return true;
} }
++cache_miss_write_count;
// Need to write // Need to write
assert(dt.buffer); assert(dt.buffer);
assert((dt.sectors > 0) && (dt.sectors <= 0x100)); assert((dt.sectors > 0) && (dt.sectors <= 0x100));

View File

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

View File

@ -54,7 +54,13 @@ public:
void Reset() override; void Reset() override;
virtual void FlushCache() { 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: protected:

View File

@ -119,7 +119,7 @@ vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
// - The SCSI/Link apparently has about 6KB buffer space for packets. // - 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; int rx_packet_size = 0;
const auto response = (scsi_resp_read_t*)buf.data(); 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; 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)); 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 // 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 // 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) { if (const int data_format = cdb[5]; data_format == 0x00) {
const int data_length = GetInt16(cdb, 3); const int data_length = GetInt16(cdb, 3);
tap.Send(buf.data(), data_length); tap.Send(buf.data(), data_length);
byte_write_count += data_length;
LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
} }
else if (data_format == 0x80) { else if (data_format == 0x80) {
// The data length is specified in the first 2 bytes of the payload // 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); const int data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
tap.Send(&(buf.data()[4]), data_length); tap.Send(&(buf.data()[4]), data_length);
byte_write_count += data_length;
LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
} }
else { else {
@ -305,7 +309,7 @@ void SCSIDaynaPort::TestUnitReady()
EnterStatusPhase(); EnterStatusPhase();
} }
void SCSIDaynaPort::Read6() const void SCSIDaynaPort::Read6()
{ {
// Get record number and block number // Get record number and block number
const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff; const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff;
@ -478,3 +482,23 @@ void SCSIDaynaPort::EnableInterface() const
EnterStatusPhase(); 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 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: public:
explicit SCSIDaynaPort(int); explicit SCSIDaynaPort(int);
@ -56,19 +62,21 @@ public:
// Commands // Commands
vector<uint8_t> InquiryInternal() const override; vector<uint8_t> InquiryInternal() const override;
int Read(cdb_t, vector<uint8_t>&, uint64_t) const; int Read(cdb_t, vector<uint8_t>&, uint64_t);
bool Write(cdb_t, span<const uint8_t>) const; bool Write(cdb_t, span<const uint8_t>);
int RetrieveStats(cdb_t, vector<uint8_t>&) const; int RetrieveStats(cdb_t, vector<uint8_t>&) const;
void TestUnitReady() override; void TestUnitReady() override;
void Read6() const; void Read6();
void Write6() const; void Write6() const;
void RetrieveStatistics() const; void RetrieveStatistics() const;
void SetInterfaceMode() const; void SetInterfaceMode() const;
void SetMcastAddr() const; void SetMcastAddr() const;
void EnableInterface() const; void EnableInterface() const;
vector<PbStatistics> GetStatistics() const override;
static const int DAYNAPORT_BUFFER_SIZE = 0x1000000; static const int DAYNAPORT_BUFFER_SIZE = 0x1000000;
static const int CMD_SCSILINK_STATS = 0x09; 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()) + LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
" bytes, " + to_string(length) + " bytes expected"); " bytes, " + to_string(length) + " bytes expected");
++print_error_count;
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
} }
@ -129,6 +131,8 @@ void SCSIPrinter::SynchronizeBuffer()
if (!out.is_open()) { if (!out.is_open()) {
LogWarn("Nothing to print"); LogWarn("Nothing to print");
++print_warning_count;
throw scsi_exception(sense_key::aborted_command); throw scsi_exception(sense_key::aborted_command);
} }
@ -145,6 +149,8 @@ void SCSIPrinter::SynchronizeBuffer()
if (system(cmd.c_str())) { if (system(cmd.c_str())) {
LogError("Printing file '" + filename + "' failed, the printing system might not be configured"); LogError("Printing file '" + filename + "' failed, the printing system might not be configured");
++print_error_count;
CleanUp(); CleanUp();
throw scsi_exception(sense_key::aborted_command); throw scsi_exception(sense_key::aborted_command);
@ -157,6 +163,8 @@ void SCSIPrinter::SynchronizeBuffer()
bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf) bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
{ {
byte_receive_count += buf.size();
if (!out.is_open()) { if (!out.is_open()) {
vector<char> f(file_template.begin(), file_template.end()); vector<char> f(file_template.begin(), file_template.end());
f.push_back(0); f.push_back(0);
@ -165,6 +173,9 @@ bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
const int fd = mkstemp(f.data()); const int fd = mkstemp(f.data());
if (fd == -1) { if (fd == -1) {
LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
++print_error_count;
return false; return false;
} }
close(fd); close(fd);
@ -173,6 +184,8 @@ bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
out.open(filename, ios::binary); out.open(filename, ios::binary);
if (out.fail()) { if (out.fail()) {
++print_error_count;
throw scsi_exception(sense_key::aborted_command); 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()); 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 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 const int NOT_RESERVED = -2;
static constexpr const char *PRINTER_FILE_PATTERN = "/piscsi_sclp-XXXXXX"; 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: public:
explicit SCSIPrinter(int); explicit SCSIPrinter(int);
@ -39,6 +49,8 @@ public:
bool WriteByteSequence(span<const uint8_t>) override; bool WriteByteSequence(span<const uint8_t>) override;
vector<PbStatistics> GetStatistics() const override;
private: private:
void TestUnitReady() override; void TestUnitReady() override;

View File

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

View File

@ -227,19 +227,62 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
result.set_status(true); result.set_status(true);
} }
void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const unordered_set<shared_ptr<PrimaryDevice>>& devices, void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const PbCommand& command,
const unordered_set<int>& reserved_ids, const string& default_folder, const string& folder_pattern, const unordered_set<shared_ptr<PrimaryDevice>>& devices, const unordered_set<int>& reserved_ids,
const string& file_pattern, int scan_depth) const const string& default_folder, int scan_depth) const
{ {
GetVersionInfo(*server_info.mutable_version_info()); const vector<string> command_operations = Split(GetParam(command, "operations"), ',');
GetLogLevelInfo(*server_info.mutable_log_level_info()); set<string, less<>> operations;
GetDeviceTypesInfo(*server_info.mutable_device_types_info()); for (const string& operation : command_operations) {
GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth); string op;
GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info()); ranges::transform(operation, back_inserter(op), ::toupper);
GetMappingInfo(*server_info.mutable_mapping_info()); operations.insert(op);
GetDevices(devices, server_info, default_folder); }
GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids);
GetOperationInfo(*server_info.mutable_operation_info(), scan_depth); 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 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 void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const
{ {
auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required"); 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, 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"); CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs");
operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder"); 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; 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/device_factory.h"
#include "devices/primary_device.h" #include "devices/primary_device.h"
#include "shared/piscsi_util.h"
#include "generated/piscsi_interface.pb.h" #include "generated/piscsi_interface.pb.h"
#include <string> #include <string>
#include <span> #include <span>
#include <set>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
@ -33,17 +35,19 @@ public:
void GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&, const string&) const; void GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&, const string&) const;
void GetDeviceTypesInfo(PbDeviceTypesInfo&) const; void GetDeviceTypesInfo(PbDeviceTypesInfo&) const;
void GetVersionInfo(PbVersionInfo&) const; void GetVersionInfo(PbVersionInfo&) const;
void GetServerInfo(PbServerInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&, const unordered_set<int>&, void GetServerInfo(PbServerInfo&, const PbCommand&, const unordered_set<shared_ptr<PrimaryDevice>>&,
const string&, const string&, const string&, int) const; const unordered_set<int>&, const string&, int) const;
void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const; void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const;
void GetMappingInfo(PbMappingInfo&) const; void GetMappingInfo(PbMappingInfo&) const;
void GetLogLevelInfo(PbLogLevelInfo&) const; void GetLogLevelInfo(PbLogLevelInfo&) const;
void GetStatisticsInfo(PbStatisticsInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&) const;
void GetOperationInfo(PbOperationInfo&, int) const; void GetOperationInfo(PbOperationInfo&, int) const;
private: private:
inline static const vector<string> EMPTY_VECTOR; 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; const DeviceFactory device_factory;
void GetDeviceProperties(const Device&, PbDeviceProperties&) const; void GetDeviceProperties(const Device&, PbDeviceProperties&) const;
@ -59,4 +63,6 @@ private:
static bool ValidateImageFile(const path&); static bool ValidateImageFile(const path&);
static bool FilterMatches(const string&, string_view); 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) // Make medium writable (not possible for read-only media)
UNPROTECT = 9; UNPROTECT = 9;
// Gets the server information (PbServerInfo). Calling this operation should be avoided because it // Gets the server information (PbServerInfo). Calling this operation without a list of operations should
// may return a lot of data. More specific other operations should be used instead. // be avoided because this may return a lot of data. More specific other operations should be used instead.
// Parameters: // 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 // "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 // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned
SERVER_INFO = 10; SERVER_INFO = 10;
@ -190,6 +193,9 @@ enum PbOperation {
// Get operation meta data (PbOperationInfo) // Get operation meta data (PbOperationInfo)
OPERATION_INFO = 31; OPERATION_INFO = 31;
// Get statistics (PbStatisticsInfo)
STATISTICS_INFO = 32;
} }
// The operation parameter meta data. The parameter data type is provided by the protobuf API. // The operation parameter meta data. The parameter data type is provided by the protobuf API.
@ -286,7 +292,7 @@ message PbImageFile {
string name = 1; string name = 1;
// The assumed device type, based on the filename extension // The assumed device type, based on the filename extension
PbDeviceType type = 2; 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; uint64 size = 3;
bool read_only = 4; bool read_only = 4;
} }
@ -311,6 +317,41 @@ message PbNetworkInterfacesInfo {
repeated string name = 1; 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 // The device definition, sent from the client to the server
message PbDeviceDefinition { message PbDeviceDefinition {
int32 id = 1; int32 id = 1;
@ -407,6 +448,8 @@ message PbResult {
PbReservedIdsInfo reserved_ids_info = 12; PbReservedIdsInfo reserved_ids_info = 12;
// The result of an OPERATION_INFO command // The result of an OPERATION_INFO command
PbOperationInfo operation_info = 13; 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; PbDevicesInfo devices_info = 8;
// The operation meta data // The operation meta data
PbOperationInfo operation_info = 9; 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: case MAPPING_INFO:
return CommandMappingInfo(); return CommandMappingInfo();
case STATISTICS_INFO:
return CommandStatisticsInfo();
case OPERATION_INFO: case OPERATION_INFO:
return CommandOperationInfo(); return CommandOperationInfo();
@ -245,16 +248,43 @@ bool ScsictlCommands::CommandServerInfo()
PbServerInfo server_info = result.server_info(); PbServerInfo server_info = result.server_info();
cout << scsictl_display.DisplayVersionInfo(server_info.version_info()); if (server_info.has_version_info()) {
cout << scsictl_display.DisplayLogLevelInfo(server_info.log_level_info()); cout << scsictl_display.DisplayVersionInfo(server_info.version_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.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() }; 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(); }); 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; return true;
} }
bool ScsictlCommands::CommandStatisticsInfo()
{
SendCommand();
cout << scsictl_display.DisplayStatisticsInfo(result.statistics_info()) << flush;
return true;
}
bool ScsictlCommands::CommandOperationInfo() bool ScsictlCommands::CommandOperationInfo()
{ {
SendCommand(); SendCommand();

View File

@ -48,6 +48,7 @@ private:
bool CommandLogLevelInfo(); bool CommandLogLevelInfo();
bool CommandReservedIdsInfo(); bool CommandReservedIdsInfo();
bool CommandMappingInfo(); bool CommandMappingInfo();
bool CommandStatisticsInfo();
bool CommandOperationInfo(); bool CommandOperationInfo();
bool SendCommand(); bool SendCommand();
bool EvaluateParams(string_view, const string&, const string&); 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] " << "[-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] " << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "
<< "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " << "[-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) << "}," << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "},"
<< " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n"
<< " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n" << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"
@ -82,7 +82,7 @@ int ScsiCtl::run(const vector<char *>& args) const
opterr = 1; opterr = 1;
int opt; int opt;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), 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) { switch (opt) {
case 'i': case 'i':
if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { if (const string error = SetIdAndLun(*device, optarg); !error.empty()) {
@ -130,7 +130,7 @@ int ScsiCtl::run(const vector<char *>& args) const
case 'e': case 'e':
command.set_operation(DEFAULT_IMAGE_FILES_INFO); command.set_operation(DEFAULT_IMAGE_FILES_INFO);
if (optarg) { if (optarg) {
SetPatternParams(command, optarg); SetCommandParams(command, optarg);
} }
break; break;
@ -208,10 +208,14 @@ int ScsiCtl::run(const vector<char *>& args) const
case 's': case 's':
command.set_operation(SERVER_INFO); command.set_operation(SERVER_INFO);
if (optarg) { if (optarg) {
SetPatternParams(command, optarg); SetCommandParams(command, optarg);
} }
break; break;
case 'S':
command.set_operation(STATISTICS_INFO);
break;
case 'v': case 'v':
cout << "scsictl version: " << piscsi_get_version_string() << '\n'; cout << "scsictl version: " << piscsi_get_version_string() << '\n';
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);

View File

@ -231,6 +231,38 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con
return s.str(); 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 string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_info) const
{ {
ostringstream s; ostringstream s;

View File

@ -32,6 +32,7 @@ public:
string DisplayImageFilesInfo(const PbImageFilesInfo&) const; string DisplayImageFilesInfo(const PbImageFilesInfo&) const;
string DisplayNetworkInterfaces(const PbNetworkInterfacesInfo&) const; string DisplayNetworkInterfaces(const PbNetworkInterfacesInfo&) const;
string DisplayMappingInfo(const PbMappingInfo&) const; string DisplayMappingInfo(const PbMappingInfo&) const;
string DisplayStatisticsInfo(const PbStatisticsInfo&) const;
string DisplayOperationInfo(const PbOperationInfo&) const; string DisplayOperationInfo(const PbOperationInfo&) const;
private: 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 folder_pattern;
string file_pattern; string file_pattern;
string operations;
if (const auto& components = Split(patterns, ':', 2); components.size() == 2) { switch (const auto& components = Split(params, COMPONENT_SEPARATOR, 3); components.size()) {
folder_pattern = components[0]; case 3:
file_pattern = components[1]; operations = components[2];
} [[fallthrough]];
else {
file_pattern = patterns; 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, "folder_pattern", folder_pattern);
SetParam(command, "file_pattern", file_pattern); SetParam(command, "file_pattern", file_pattern);
SetParam(command, "operations", operations);
} }
void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data) void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data)

View File

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

View File

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

View File

@ -53,27 +53,38 @@ TEST(ProtobufUtil, ParseParameters)
TestSpecialDevice("services"); TestSpecialDevice("services");
} }
TEST(ProtobufUtil, SetPatternParams) TEST(ProtobufUtil, SetCommandParams)
{ {
PbCommand command1; PbCommand command1;
SetPatternParams(command1, "file"); SetCommandParams(command1, "file");
EXPECT_EQ("", GetParam(command1, "folder_pattern")); EXPECT_EQ("", GetParam(command1, "folder_pattern"));
EXPECT_EQ("file", GetParam(command1, "file_pattern")); EXPECT_EQ("file", GetParam(command1, "file_pattern"));
PbCommand command2; PbCommand command2;
SetPatternParams(command2, ":file"); SetCommandParams(command2, ":file");
EXPECT_EQ("", GetParam(command2, "folder_pattern")); EXPECT_EQ("", GetParam(command2, "folder_pattern"));
EXPECT_EQ("file", GetParam(command2, "file_pattern")); EXPECT_EQ("file", GetParam(command2, "file_pattern"));
PbCommand command3; PbCommand command3;
SetPatternParams(command3, "folder:"); SetCommandParams(command3, "file:");
EXPECT_EQ("folder", GetParam(command3, "folder_pattern")); EXPECT_EQ("file", GetParam(command3, "file_pattern"));
EXPECT_EQ("", GetParam(command3, "file_pattern")); EXPECT_EQ("", GetParam(command3, "folder_pattern"));
PbCommand command4; PbCommand command4;
SetPatternParams(command4, "folder:file"); SetCommandParams(command4, "folder:file");
EXPECT_EQ("folder", GetParam(command4, "folder_pattern")); EXPECT_EQ("folder", GetParam(command4, "folder_pattern"));
EXPECT_EQ("file", GetParam(command4, "file_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) TEST(ProtobufUtil, ListDevices)

View File

@ -7,30 +7,31 @@ scsictl \- Sends management commands to the piscsi process
\fB\-l\fR | \fB\-l\fR |
\fB\-m\fR | \fB\-m\fR |
\fB\-o\fR | \fB\-o\fR |
\fB\-s\fR |
\fB\-v\fR | \fB\-v\fR |
\fB\-D\fR | \fB\-D\fR |
\fB\-I\fR | \fB\-I\fR |
\fB\-L\fR | \fB\-L\fR |
\fB\-O\fR | \fB\-O\fR |
\fB\-P\fR | \fB\-P\fR |
\fB\-S\fR |
\fB\-T\fR | \fB\-T\fR |
\fB\-V\fR | \fB\-V\fR |
\fB\-X\fR | \fB\-X\fR |
[\fB\-C\fR \fIFILENAME:FILESIZE\fR] [\fB\-C\fR \fIFILENAME:FILESIZE\fR] |
[\fB\-E\fR \fIFILENAME\fR] [\fB\-E\fR \fIFILENAME\fR] |
[\fB\-F\fR \fIIMAGE_FOLDER\fR] [\fB\-F\fR \fIIMAGE_FOLDER\fR] |
[\fB\-R\fR \fICURRENT_NAME:NEW_NAME\fR] [\fB\-R\fR \fICURRENT_NAME:NEW_NAME\fR] |
[\fB\-c\fR \fICMD\fR] [\fB\-c\fR \fICMD\fR] |
[\fB\-f\fR \fIFILE|PARAM\fR] [\fB\-f\fR \fIFILE|PARAM\fR] |
[\fB\-g\fR \fILOG_LEVEL\fR] [\fB\-g\fR \fILOG_LEVEL\fR] |
[\fB\-h\fR \fIHOST\fR] [\fB\-h\fR \fIHOST\fR] |
[\fB\-i\fR \fIID[:LUN]\fR [\fB\-i\fR \fIID[:LUN]\fR] |
[\fB\-n\fR \fINAME\fR] [\fB\-n\fR \fINAME\fR] |
[\fB\-p\fR \fIPORT\fR] [\fB\-p\fR \fIPORT\fR] |
[\fB\-r\fR \fIRESERVED_IDS\fR] [\fB\-r\fR \fIRESERVED_IDS\fR] |
[\fB\-t\fR \fITYPE\fR] [\fB\-s\fR \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]\fR] |
[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\fR] [\fB\-t\fR \fITYPE\fR] |
[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\fR] |
[\fB\-z\fR \fILOCALE\fR] [\fB\-z\fR \fILOCALE\fR]
.SH DESCRIPTION .SH DESCRIPTION
.B scsictl .B scsictl
@ -38,7 +39,7 @@ sends commands to the piscsi process to make configuration adjustments at runtim
Either the -i or -l option should be specified at one time. Not both. Either the -i or -l option should be specified at one time. Not both.
You do NOT need root privileges to use scsictl. You do NOT need root privileges to use scsictl. scsictl also runs on non-Pi Linux platforms.
Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool. Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool.
@ -95,9 +96,12 @@ The piscsi port to connect to, default is 6868.
.BR \-r\fI " " \fIRESERVED_IDS .BR \-r\fI " " \fIRESERVED_IDS
Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything.
.TP .TP
.BR \-s\fI .BR \-s\fI " " \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]
Display server-side settings like available images or supported device types. Display server-side settings like available images or supported device types.
.TP .TP
.BR \-S\fI
Display statistics.
.TP
.BR \-T\fI .BR \-T\fI
Display all device types and their properties. Display all device types and their properties.
.TP .TP

View File

@ -6,19 +6,21 @@ NAME
scsictl - Sends management commands to the piscsi process scsictl - Sends management commands to the piscsi process
SYNOPSIS SYNOPSIS
scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V scsictl -e | -l | -m | -o | -v | -D | -I | -L | -O | -P | -S | -T | -V
| -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR | -X | [-C FILENAME:FILESIZE] | [-E FILENAME] | [-F IMAGE_FOLDER] | [-R
RENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] CURRENT_NAME:NEW_NAME] | [-c CMD] | [-f FILE|PARAM] | [-g LOG_LEVEL] |
[-i ID[:LUN] [-n NAME] [-p PORT] [-r RESERVED_IDS] [-t TYPE] [-x CUR [-h HOST] | [-i ID[:LUN]] | [-n NAME] | [-p PORT] | [-r RESERVED_IDS] |
RENT_NAME:NEW_NAME] [-z LOCALE] [-s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]] | [-t TYPE] | [-x CUR
RENT_NAME:NEW_NAME] | [-z LOCALE]
DESCRIPTION DESCRIPTION
scsictl sends commands to the piscsi process to make configuration ad scsictl sends commands to the piscsi process to make configuration ad
justments at runtime or to check the status of the devices. justments at runtime or to check the status of the devices.
Either the -i or -l option should be specified at one time. Not both. Either the -i or -l option should be specified at one time. Not both.
You do NOT need root privileges to use scsictl. You do NOT need root privileges to use scsictl. scsictl also runs on
non-Pi Linux platforms.
Note: The command and type arguments are case insensitive. Only the Note: The command and type arguments are case insensitive. Only the
first letter of the command/type is evaluated by the tool. first letter of the command/type is evaluated by the tool.
@ -74,9 +76,12 @@ OPTIONS
Comma-separated list of IDs to reserve. Pass an empty list in Comma-separated list of IDs to reserve. Pass an empty list in
order to not reserve anything. order to not reserve anything.
-s Display server-side settings like available images or supported -s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]
Display server-side settings like available images or supported
device types. device types.
-S Display statistics.
-T Display all device types and their properties. -T Display all device types and their properties.
-v Display the piscsi server version. -v Display the piscsi server version.