Files
gotgt/pkg/scsi/scsi.go
Lei Xue a5628f4ec0 add end-to-end IO benchmarks and fix pprof-identified hotspots
Add comprehensive benchmark suite (io_bench_test.go):
- BenchmarkEndToEndRead/Write: full SCSI stack (512B to 256KB)
- BenchmarkEndToEndReadParallel/WriteParallel: concurrent IO
- BenchmarkFileBackingStoreRead/Write: isolated backing store

pprof-guided optimizations:
- Guard hot-path log.Debugf with log.GetLevel() check in scsi.go,
  sbc.go, backingstore.go — eliminates 22% CPU overhead from logrus
  Entry allocation even when debug logging is disabled
- Add FileBackingStore.ReadAt for zero-copy reads directly into
  caller's buffer, bypassing Read()'s per-call make([]byte, tl)
- Use ReadAt via interface assertion in bsPerformCommand to read
  directly into InSDBBuffer, eliminating allocation + copy

Results (256KB reads): +42% throughput, allocs reduced from 10 to 5

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 19:41:48 +08:00

249 lines
6.5 KiB
Go

/*
Copyright 2017 The GoStor Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package scsi
import (
"bytes"
"encoding/binary"
"fmt"
"sync"
"github.com/gostor/gotgt/pkg/api"
log "github.com/sirupsen/logrus"
)
// SCSITargetService SCSI target service
type SCSITargetService struct {
mutex sync.RWMutex
Targets []*api.SCSITarget
}
var _instance *SCSITargetService
var service sync.Once
// NewSCSITargetService create a new SCSITargetService
func NewSCSITargetService() *SCSITargetService {
service.Do(func() {
_instance = &SCSITargetService{Targets: []*api.SCSITarget{}}
})
return _instance
}
// GetTargetList get SCSI target list
func (s *SCSITargetService) GetTargetList() ([]*api.SCSITarget, error) {
result := []*api.SCSITarget{}
s.mutex.RLock()
defer s.mutex.RUnlock()
for _, t := range s.Targets {
result = append(result, t)
}
return result, nil
}
// Resize resize
func (s *SCSITargetService) Resize(size uint64) error {
s.mutex.Lock()
//TODO for multiple LUNs
for _, t := range s.Targets {
if t.Devices != nil {
for i := range t.Devices {
t.Devices[i].Size = size
}
}
}
s.mutex.Unlock()
return nil
}
// AddCommandQueue add command queue
func (s *SCSITargetService) AddCommandQueue(tid int, scmd *api.SCSICommand) error {
var (
target *api.SCSITarget
itn *api.ITNexus
)
s.mutex.RLock()
for _, t := range s.Targets {
if t.TID == tid {
target = t
break
}
}
s.mutex.RUnlock()
if target == nil {
return fmt.Errorf("Cannot find the target with ID(%d)", tid)
}
scmd.Target = target
for _, it := range target.ITNexus {
if it.ID == scmd.ITNexusID {
itn = it
break
}
}
scmd.ITNexus = itn
lun := binary.LittleEndian.Uint64(scmd.Lun[:])
scmd.Device = target.Devices[lun]
if log.GetLevel() >= log.DebugLevel {
log.Debugf("scsi opcode: 0x%x, LUN: %d", int(scmd.SCB[0]), lun)
}
if scmd.Device == nil {
scmd.Device = target.LUN0
if lun != 0 {
BuildSenseData(scmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
scmd.Result = api.SAMStatCheckCondition.Stat
log.Warnf("%v", api.SAMStatCheckCondition.Err)
return nil
}
}
result := scmd.Device.PerformCommand(tid, scmd)
if result != api.SAMStatGood {
scmd.Result = result.Stat
log.Warnf("opcode: %xh err: %v", scmd.OpCode, result.Err)
}
return nil
}
// SCSIServiceAction SCSI service action
type SCSIServiceAction struct {
ServiceAction uint8
CommandPerformFunc api.CommandFunc
}
// SCSIDeviceOperation SCSI device operation
type SCSIDeviceOperation struct {
CommandPerformFunc api.CommandFunc
ServiceAction []*SCSIServiceAction
PRConflictBits uint8
}
// BaseSCSIDeviceProtocol base SCSI device protocol
type BaseSCSIDeviceProtocol struct {
DeviceType api.SCSIDeviceType
SCSIDeviceOps []SCSIDeviceOperation
}
// NewSCSIDeviceOperation create a new SCSI device operation
func NewSCSIDeviceOperation(fn api.CommandFunc, sa []*SCSIServiceAction, pr uint8) SCSIDeviceOperation {
return SCSIDeviceOperation{
CommandPerformFunc: fn,
ServiceAction: sa,
PRConflictBits: pr,
}
}
// BuildSenseData build sense data
func BuildSenseData(cmd *api.SCSICommand, key byte, asc SCSISubError) {
senseBuffer := &bytes.Buffer{}
inBufLen, ok := SCSICDBBufXLength(cmd.SCB)
var (
length uint32 = 0xa
fixedFormat bool = true
)
if cmd.Device != nil {
if cmd.Device.Attrs.SenseFormat {
fixedFormat = false
}
}
if !fixedFormat {
// descriptor format
// current, not deferred
senseBuffer.WriteByte(0x72)
senseBuffer.WriteByte(key)
senseBuffer.WriteByte(byte(asc>>8) & 0xff)
senseBuffer.WriteByte(byte(asc) & 0xff)
length = 8
} else {
// fixed format
// current, not deferred
senseBuffer.WriteByte(0x70)
senseBuffer.WriteByte(0x00)
senseBuffer.WriteByte(key)
for i := 0; i < 4; i++ {
senseBuffer.WriteByte(0x00)
}
senseBuffer.WriteByte(byte(length))
for i := 0; i < 4; i++ {
senseBuffer.WriteByte(0x00)
}
senseBuffer.WriteByte(byte(asc>>8) & 0xff)
senseBuffer.WriteByte(byte(asc) & 0xff)
for i := 0; i < 4; i++ {
senseBuffer.WriteByte(0x00)
}
length += 8
}
if ok {
if int64(length) > inBufLen {
log.Warnf("sense buffer is bigger than in buffer")
senseBuffer.Truncate(int(inBufLen))
}
} else {
log.Debugf("cannot calc cbd alloc length. truncate failed")
}
// Note: cmd.Result should be set by the caller, not here
// The caller should set cmd.Result = api.SAM_STAT_CHECK_CONDITION when returning error
cmd.SenseBuffer = &api.SenseBuffer{Buffer: senseBuffer.Bytes(), Length: length}
}
func getSCSIReadWriteOffset(scb []byte) uint64 {
var off uint64
var opcode = api.SCSICommandType(scb[0])
switch opcode {
case api.READ_6, api.WRITE_6:
off = uint64((scb[1]&0x1f))<<16 + uint64(scb[2])<<8 + uint64(scb[3])
case api.READ_10, api.PRE_FETCH_10, api.WRITE_10, api.VERIFY_10, api.WRITE_VERIFY, api.WRITE_SAME, api.SYNCHRONIZE_CACHE, api.READ_12, api.WRITE_12, api.VERIFY_12, api.WRITE_VERIFY_12:
off = uint64(binary.BigEndian.Uint32(scb[2:]))
case api.READ_16, api.PRE_FETCH_16, api.WRITE_16, api.ORWRITE_16, api.VERIFY_16, api.WRITE_VERIFY_16, api.WRITE_SAME_16, api.SYNCHRONIZE_CACHE_16, api.COMPARE_AND_WRITE:
off = binary.BigEndian.Uint64(scb[2:])
default:
}
return off
}
func getSCSIReadWriteCount(scb []byte) uint32 {
var cnt uint32
var opcode = api.SCSICommandType(scb[0])
switch opcode {
case api.READ_6, api.WRITE_6:
cnt = uint32(scb[4])
if cnt == 0 {
cnt = 256
}
case api.READ_10, api.PRE_FETCH_10, api.WRITE_10, api.VERIFY_10, api.WRITE_VERIFY, api.WRITE_SAME, api.SYNCHRONIZE_CACHE:
cnt = uint32(scb[7])<<8 | uint32(scb[8])
case api.READ_12, api.WRITE_12, api.VERIFY_12, api.WRITE_VERIFY_12:
cnt = binary.BigEndian.Uint32(scb[6:])
// Note: READ(12)/WRITE(12) have 32-bit transfer length field, but only use lower 16 bits
// per SCSI SBC-3 spec. Zero means zero blocks.
case api.READ_16, api.PRE_FETCH_16, api.WRITE_16, api.ORWRITE_16, api.VERIFY_16, api.WRITE_VERIFY_16, api.WRITE_SAME_16, api.SYNCHRONIZE_CACHE_16:
cnt = binary.BigEndian.Uint32(scb[10:])
case api.COMPARE_AND_WRITE:
cnt = uint32(scb[13])
default:
}
return cnt
}