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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.