From b7cb23e391a37dd9beba2304ed8229175f236518 Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:32:45 +0100 Subject: [PATCH] Add statistics and make scsictl accept generic key/value parameters (#1237/#1238) (#1262) * Add statistics and make scsictl accept generic key/value parameters --- cpp/devices/disk.cpp | 39 ++++++++++++++ cpp/devices/disk.h | 10 +++- cpp/devices/disk_cache.cpp | 45 +++++++++++++--- cpp/devices/disk_cache.h | 17 +++++- cpp/devices/disk_track.cpp | 8 ++- cpp/devices/disk_track.h | 5 +- cpp/devices/primary_device.h | 8 ++- cpp/devices/scsi_daynaport.cpp | 30 +++++++++-- cpp/devices/scsi_daynaport.h | 14 +++-- cpp/devices/scsi_printer.cpp | 53 +++++++++++++++++- cpp/devices/scsi_printer.h | 12 +++++ cpp/piscsi/piscsi_core.cpp | 10 ++-- cpp/piscsi/piscsi_response.cpp | 89 ++++++++++++++++++++++++++----- cpp/piscsi/piscsi_response.h | 10 +++- cpp/piscsi_interface.proto | 51 ++++++++++++++++-- cpp/scsictl/scsictl_commands.cpp | 57 ++++++++++++++++---- cpp/scsictl/scsictl_commands.h | 1 + cpp/scsictl/scsictl_core.cpp | 12 +++-- cpp/scsictl/scsictl_display.cpp | 32 +++++++++++ cpp/scsictl/scsictl_display.h | 1 + cpp/shared/protobuf_util.cpp | 26 ++++++--- cpp/shared/protobuf_util.h | 2 +- cpp/test/piscsi_response_test.cpp | 46 ++++++++++++---- cpp/test/protobuf_util_test.cpp | 25 ++++++--- doc/scsictl.1 | 38 +++++++------ doc/scsictl_man_page.txt | 21 +++++--- 26 files changed, 557 insertions(+), 105 deletions(-) diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index 8a32273e..bc8b53d0 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -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 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 buf, uint64_t block) if (!cache->WriteSector(buf, static_cast(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 Disk::GetStatistics() const +{ + vector 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; +} diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index 1b22262d..7364ad08 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -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 GetStatistics() const override; + private: // Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm) diff --git a/cpp/devices/disk_cache.cpp b/cpp/devices/disk_cache.cpp index 23bd724a..7ab69c19 100644 --- a/cpp/devices/disk_cache.cpp +++ b/cpp/devices/disk_cache.cpp @@ -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 DiskCache::GetTrack(uint32_t block) @@ -120,7 +120,7 @@ shared_ptr 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 disktrk) sectors = 0x100; } - // Create a disk track if (disktrk == nullptr) { disktrk = make_shared(); } - // 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 DiskCache::GetStatistics(bool is_read_only) const +{ + vector 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; +} diff --git a/cpp/devices/disk_cache.h b/cpp/devices/disk_cache.h index 4400e27d..ec486edd 100644 --- a/cpp/devices/disk_cache.h +++ b/cpp/devices/disk_cache.h @@ -15,18 +15,30 @@ #pragma once +#include "generated/piscsi_interface.pb.h" #include #include #include #include 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, uint32_t); // Sector Read bool WriteSector(span, uint32_t); // Sector Write + vector GetStatistics(bool) const; + private: // Internal Management diff --git a/cpp/devices/disk_track.cpp b/cpp/devices/disk_track.cpp index 35e1e56b..e2e2e01d 100644 --- a/cpp/devices/disk_track.cpp +++ b/cpp/devices/disk_track.cpp @@ -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)); diff --git a/cpp/devices/disk_track.h b/cpp/devices/disk_track.h index 91ca5e86..c61c2f29 100644 --- a/cpp/devices/disk_track.h +++ b/cpp/devices/disk_track.h @@ -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, int) const; // Sector Read bool WriteSector(span buf, int); // Sector Write diff --git a/cpp/devices/primary_device.h b/cpp/devices/primary_device.h index 682a4f97..c30bc123 100644 --- a/cpp/devices/primary_device.h +++ b/cpp/devices/primary_device.h @@ -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 GetStatistics() const { + // Devices which provide statistics have to override this method + + return vector(); } protected: diff --git a/cpp/devices/scsi_daynaport.cpp b/cpp/devices/scsi_daynaport.cpp index db46ea16..a945bd17 100644 --- a/cpp/devices/scsi_daynaport.cpp +++ b/cpp/devices/scsi_daynaport.cpp @@ -119,7 +119,7 @@ vector SCSIDaynaPort::InquiryInternal() const // - The SCSI/Link apparently has about 6KB buffer space for packets. // //--------------------------------------------------------------------------- -int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const +int SCSIDaynaPort::Read(cdb_t cdb, vector& 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& 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& buf, uint64_t) const // XX XX ... is the actual packet // //--------------------------------------------------------------------------- -bool SCSIDaynaPort::Write(cdb_t cdb, span buf) const +bool SCSIDaynaPort::Write(cdb_t cdb, span 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(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 SCSIDaynaPort::GetStatistics() const +{ + vector 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; +} diff --git a/cpp/devices/scsi_daynaport.h b/cpp/devices/scsi_daynaport.h index dc2ede6a..94f45551 100644 --- a/cpp/devices/scsi_daynaport.h +++ b/cpp/devices/scsi_daynaport.h @@ -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 InquiryInternal() const override; - int Read(cdb_t, vector&, uint64_t) const; - bool Write(cdb_t, span) const; + int Read(cdb_t, vector&, uint64_t); + bool Write(cdb_t, span); int RetrieveStats(cdb_t, vector&) 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 GetStatistics() const override; + static const int DAYNAPORT_BUFFER_SIZE = 0x1000000; static const int CMD_SCSILINK_STATS = 0x09; diff --git a/cpp/devices/scsi_printer.cpp b/cpp/devices/scsi_printer.cpp index f4399985..393e053e 100644 --- a/cpp/devices/scsi_printer.cpp +++ b/cpp/devices/scsi_printer.cpp @@ -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 buf) { + byte_receive_count += buf.size(); + if (!out.is_open()) { vector f(file_template.begin(), file_template.end()); f.push_back(0); @@ -165,6 +173,9 @@ bool SCSIPrinter::WriteByteSequence(span 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 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 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 SCSIPrinter::GetStatistics() const +{ + vector 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; } diff --git a/cpp/devices/scsi_printer.h b/cpp/devices/scsi_printer.h index e9f6c92d..50ef94b9 100644 --- a/cpp/devices/scsi_printer.h +++ b/cpp/devices/scsi_printer.h @@ -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) override; + vector GetStatistics() const override; + private: void TestUnitReady() override; diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index e2bf056e..1be72793 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -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); diff --git a/cpp/piscsi/piscsi_response.cpp b/cpp/piscsi/piscsi_response.cpp index 16ac25b5..28e7e1a2 100644 --- a/cpp/piscsi/piscsi_response.cpp +++ b/cpp/piscsi/piscsi_response.cpp @@ -227,19 +227,62 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set>& devices, - const unordered_set& 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>& devices, const unordered_set& 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 command_operations = Split(GetParam(command, "operations"), ','); + set> 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>& 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>& operations, PbOperation operation) +{ + return operations.empty() || operations.contains(PbOperation_Name(operation)); +} diff --git a/cpp/piscsi/piscsi_response.h b/cpp/piscsi/piscsi_response.h index 549ea905..b095416f 100644 --- a/cpp/piscsi/piscsi_response.h +++ b/cpp/piscsi/piscsi_response.h @@ -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 #include +#include using namespace std; using namespace filesystem; @@ -33,17 +35,19 @@ public: void GetDevicesInfo(const unordered_set>&, PbResult&, const PbCommand&, const string&) const; void GetDeviceTypesInfo(PbDeviceTypesInfo&) const; void GetVersionInfo(PbVersionInfo&) const; - void GetServerInfo(PbServerInfo&, const unordered_set>&, const unordered_set&, - const string&, const string&, const string&, int) const; + void GetServerInfo(PbServerInfo&, const PbCommand&, const unordered_set>&, + const unordered_set&, const string&, int) const; void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const; void GetMappingInfo(PbMappingInfo&) const; void GetLogLevelInfo(PbLogLevelInfo&) const; + void GetStatisticsInfo(PbStatisticsInfo&, const unordered_set>&) const; void GetOperationInfo(PbOperationInfo&, int) const; private: inline static const vector 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>&, PbOperation); }; diff --git a/cpp/piscsi_interface.proto b/cpp/piscsi_interface.proto index 1a1be45c..77706644 100644 --- a/cpp/piscsi_interface.proto +++ b/cpp/piscsi_interface.proto @@ -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; } diff --git a/cpp/scsictl/scsictl_commands.cpp b/cpp/scsictl/scsictl_commands.cpp index 8cb55f60..53dea40b 100644 --- a/cpp/scsictl/scsictl_commands.cpp +++ b/cpp/scsictl/scsictl_commands.cpp @@ -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 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(); diff --git a/cpp/scsictl/scsictl_commands.h b/cpp/scsictl/scsictl_commands.h index 11aa51fa..4477a14c 100644 --- a/cpp/scsictl/scsictl_commands.h +++ b/cpp/scsictl/scsictl_commands.h @@ -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&); diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index ff24d455..9efad57a 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -35,7 +35,7 @@ void ScsiCtl::Banner(const vector& 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& args) const opterr = 1; int opt; while ((opt = getopt(static_cast(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& 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& 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); diff --git a/cpp/scsictl/scsictl_display.cpp b/cpp/scsictl/scsictl_display.cpp index b71782c6..7250910a 100644 --- a/cpp/scsictl/scsictl_display.cpp +++ b/cpp/scsictl/scsictl_display.cpp @@ -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 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; diff --git a/cpp/scsictl/scsictl_display.h b/cpp/scsictl/scsictl_display.h index 1caddf48..94ebcf93 100644 --- a/cpp/scsictl/scsictl_display.h +++ b/cpp/scsictl/scsictl_display.h @@ -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: diff --git a/cpp/shared/protobuf_util.cpp b/cpp/shared/protobuf_util.cpp index 012a5011..17239bd0 100644 --- a/cpp/shared/protobuf_util.cpp +++ b/cpp/shared/protobuf_util.cpp @@ -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) diff --git a/cpp/shared/protobuf_util.h b/cpp/shared/protobuf_util.h index 0e5b2a78..cc155915 100644 --- a/cpp/shared/protobuf_util.h +++ b/cpp/shared/protobuf_util.h @@ -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&); diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index 278880d7..5fe6ac1a 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -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 using namespace piscsi_interface; +using namespace protobuf_util; TEST(PiscsiResponseTest, Operation_Count) { @@ -178,15 +180,41 @@ TEST(PiscsiResponseTest, GetServerInfo) const unordered_set> devices; const unordered_set 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) diff --git a/cpp/test/protobuf_util_test.cpp b/cpp/test/protobuf_util_test.cpp index 1c4a06e6..2793a657 100644 --- a/cpp/test/protobuf_util_test.cpp +++ b/cpp/test/protobuf_util_test.cpp @@ -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) diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 188c6930..3df74fba 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -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 -[\fB\-n\fR \fINAME\fR] -[\fB\-p\fR \fIPORT\fR] -[\fB\-r\fR \fIRESERVED_IDS\fR] -[\fB\-t\fR \fITYPE\fR] -[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\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] | +[\fB\-n\fR \fINAME\fR] | +[\fB\-p\fR \fIPORT\fR] | +[\fB\-r\fR \fIRESERVED_IDS\fR] | +[\fB\-s\fR \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]\fR] | +[\fB\-t\fR \fITYPE\fR] | +[\fB\-x\fR \fICURRENT_NAME:NEW_NAME\fR] | [\fB\-z\fR \fILOCALE\fR] .SH DESCRIPTION .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. -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. @@ -95,9 +96,12 @@ The piscsi port to connect to, default is 6868. .BR \-r\fI " " \fIRESERVED_IDS Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. .TP -.BR \-s\fI +.BR \-s\fI " " \fI[FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] Display server-side settings like available images or supported device types. .TP +.BR \-S\fI +Display statistics. +.TP .BR \-T\fI Display all device types and their properties. .TP diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index 1b5108fd..f98b7208 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -6,19 +6,21 @@ NAME scsictl - Sends management commands to the piscsi process SYNOPSIS - scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V - | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR‐ - RENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] - [-i ID[:LUN] [-n NAME] [-p PORT] [-r RESERVED_IDS] [-t TYPE] [-x CUR‐ - RENT_NAME:NEW_NAME] [-z LOCALE] + scsictl -e | -l | -m | -o | -v | -D | -I | -L | -O | -P | -S | -T | -V + | -X | [-C FILENAME:FILESIZE] | [-E FILENAME] | [-F IMAGE_FOLDER] | [-R + CURRENT_NAME:NEW_NAME] | [-c CMD] | [-f FILE|PARAM] | [-g LOG_LEVEL] | + [-h HOST] | [-i ID[:LUN]] | [-n NAME] | [-p PORT] | [-r RESERVED_IDS] | + [-s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]] | [-t TYPE] | [-x CUR‐ + RENT_NAME:NEW_NAME] | [-z LOCALE] 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. 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. @@ -74,9 +76,12 @@ OPTIONS Comma-separated list of IDs to reserve. Pass an empty list in 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. + -S Display statistics. + -T Display all device types and their properties. -v Display the piscsi server version.