feat(storageminer): add api for transfer diagnostics

Add API + CLI for inspecting in depth diagnostics on graphsync transfers with a given peer
This commit is contained in:
hannahhoward
2021-12-22 13:41:29 -08:00
parent 1bc987772c
commit cddf63efe9
16 changed files with 254 additions and 19 deletions
+3 -1
View File
@@ -164,7 +164,9 @@ func ConfigStorageMiner(c interface{}) Option {
Override(HandleRetrievalKey, modules.HandleRetrieval),
// Markets (storage)
Override(new(dtypes.ProviderDataTransfer), modules.NewProviderDAGServiceDataTransfer),
Override(new(dtypes.ProviderTransferNetwork), modules.NewProviderTransferNetwork),
Override(new(dtypes.ProviderTransport), modules.NewProviderTransport),
Override(new(dtypes.ProviderDataTransfer), modules.NewProviderDataTransfer),
Override(new(*storedask.StoredAsk), modules.NewStorageAsk),
Override(new(dtypes.StorageDealFilter), modules.BasicDealFilter(cfg.Dealmaking, nil)),
Override(new(storagemarket.StorageProvider), modules.StorageProvider),
+107
View File
@@ -19,6 +19,9 @@ import (
"github.com/google/uuid"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-graphsync"
gsimpl "github.com/ipfs/go-graphsync/impl"
"github.com/ipfs/go-graphsync/peerstate"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
"go.uber.org/fx"
@@ -28,6 +31,7 @@ import (
"github.com/filecoin-project/go-address"
datatransfer "github.com/filecoin-project/go-data-transfer"
gst "github.com/filecoin-project/go-data-transfer/transport/graphsync"
"github.com/filecoin-project/go-fil-markets/piecestore"
"github.com/filecoin-project/go-fil-markets/retrievalmarket"
"github.com/filecoin-project/go-fil-markets/storagemarket"
@@ -70,6 +74,8 @@ type StorageMinerAPI struct {
RetrievalProvider retrievalmarket.RetrievalProvider `optional:"true"`
SectorAccessor retrievalmarket.SectorAccessor `optional:"true"`
DataTransfer dtypes.ProviderDataTransfer `optional:"true"`
StagingGraphsync dtypes.StagingGraphsync `optional:"true"`
Transport dtypes.ProviderTransport `optional:"true"`
DealPublisher *storageadapter.DealPublisher `optional:"true"`
SectorBlocks *sectorblocks.SectorBlocks `optional:"true"`
Host host.Host `optional:"true"`
@@ -553,6 +559,107 @@ func (sm *StorageMinerAPI) MarketDataTransferUpdates(ctx context.Context) (<-cha
return channels, nil
}
func (sm *StorageMinerAPI) MarketDataTransferDiagnostics(ctx context.Context, mpid peer.ID) (*api.TransferDiagnostics, error) {
// gather information about active transport channels
transportChannels := sm.Transport.(*gst.Transport).ChannelsForPeer(mpid)
// gather information about graphsync state for peer
gsPeerState := sm.StagingGraphsync.(*gsimpl.GraphSync).PeerState(mpid)
sendingTransfers := sm.generateTransfers(ctx, transportChannels.SendingChannels, gsPeerState.IncomingState)
receivingTransfers := sm.generateTransfers(ctx, transportChannels.ReceivingChannels, gsPeerState.OutgoingState)
return &api.TransferDiagnostics{
SendingTransfers: sendingTransfers,
ReceivingTransfers: receivingTransfers,
}, nil
}
// generate transfers matches graphsync state and data transfer state for a given peer
// to produce detailed output on what's happening with a transfer
func (sm *StorageMinerAPI) generateTransfers(ctx context.Context,
transportChannels map[datatransfer.ChannelID]gst.ChannelGraphsyncRequests,
gsPeerState peerstate.PeerState) []*api.GraphSyncDataTransfer {
tc := &transferConverter{
matchedRequests: make(map[graphsync.RequestID]*api.GraphSyncDataTransfer),
gsDiagnostics: gsPeerState.Diagnostics(),
requestStates: gsPeerState.RequestStates,
}
// iterate through all operating data transfer transport channels
for channelID, channelRequests := range transportChannels {
originalState, err := sm.DataTransfer.ChannelState(ctx, channelID)
var baseDiagnostics []string
var channelState *api.DataTransferChannel
if err != nil {
baseDiagnostics = append(baseDiagnostics, fmt.Sprintf("Unable to lookup channel state: %s", err))
} else {
cs := api.NewDataTransferChannel(sm.Host.ID(), originalState)
channelState = &cs
}
// add the current request for this channel
tc.convertTransfer(&channelID, channelState, baseDiagnostics, channelRequests.Current, true)
for _, requestID := range channelRequests.Previous {
// add any previous requests that were cancelled for a restart
tc.convertTransfer(&channelID, channelState, baseDiagnostics, requestID, false)
}
}
// collect any graphsync data for channels we don't have any data transfer data for
tc.collectRemainingTransfers()
return tc.transfers
}
type transferConverter struct {
matchedRequests map[graphsync.RequestID]*api.GraphSyncDataTransfer
transfers []*api.GraphSyncDataTransfer
gsDiagnostics map[graphsync.RequestID][]string
requestStates graphsync.RequestStates
}
// convert transfer assembles transfer and diagnostic data for a given graphsync/data-transfer request
func (tc *transferConverter) convertTransfer(channelID *datatransfer.ChannelID, channelState *api.DataTransferChannel, baseDiagnostics []string,
requestID graphsync.RequestID, isCurrentChannelRequest bool) {
diagnostics := baseDiagnostics
state, hasState := tc.requestStates[requestID]
stateString := state.String()
if !hasState {
stateString = "no graphsync state found"
}
if channelID == nil {
diagnostics = append(diagnostics, fmt.Sprintf("No data transfer channel id for GraphSync request ID %d", requestID))
} else if isCurrentChannelRequest && !hasState {
diagnostics = append(diagnostics, fmt.Sprintf("No current request state for data transfer channel id %s", channelID))
} else if !isCurrentChannelRequest && hasState {
diagnostics = append(diagnostics, fmt.Sprintf("Graphsync request %d is a previous request on data transfer channel id %s that was restarted, but it is still running", requestID, channelID))
}
diagnostics = append(diagnostics, tc.gsDiagnostics[requestID]...)
transfer := &api.GraphSyncDataTransfer{
RequestID: requestID,
RequestState: stateString,
IsCurrentChannelRequest: isCurrentChannelRequest,
ChannelID: channelID,
ChannelState: channelState,
Diagnostics: diagnostics,
}
tc.transfers = append(tc.transfers, transfer)
tc.matchedRequests[requestID] = transfer
}
func (tc *transferConverter) collectRemainingTransfers() {
for requestID := range tc.requestStates {
if _, ok := tc.matchedRequests[requestID]; !ok {
tc.convertTransfer(nil, nil, nil, requestID, false)
}
}
for requestID := range tc.gsDiagnostics {
if _, ok := tc.matchedRequests[requestID]; !ok {
tc.convertTransfer(nil, nil, nil, requestID, false)
}
}
}
func (sm *StorageMinerAPI) MarketPendingDeals(ctx context.Context) (api.PendingDealInfo, error) {
return sm.DealPublisher.PendingDeals(), nil
}
+3 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/ipfs/go-graphsync"
exchange "github.com/ipfs/go-ipfs-exchange-interface"
dtnet "github.com/filecoin-project/go-data-transfer/network"
"github.com/filecoin-project/go-fil-markets/piecestore"
"github.com/filecoin-project/go-fil-markets/storagemarket/impl/requestvalidation"
@@ -85,6 +86,7 @@ type ProviderRequestValidator *requestvalidation.UnifiedRequestValidator
// ProviderDataTransfer is a data transfer manager for the provider
type ProviderDataTransfer datatransfer.Manager
type ProviderTransferNetwork dtnet.DataTransferNetwork
type ProviderTransport datatransfer.Transport
type StagingBlockstore blockstore.BasicBlockstore
type StagingGraphsync graphsync.GraphExchange
+11 -5
View File
@@ -335,13 +335,19 @@ func HandleMigrateProviderFunds(lc fx.Lifecycle, ds dtypes.MetadataDS, node api.
})
}
// NewProviderDAGServiceDataTransfer returns a data transfer manager that just
// uses the provider's Staging DAG service for transfers
func NewProviderDAGServiceDataTransfer(lc fx.Lifecycle, h host.Host, gs dtypes.StagingGraphsync, ds dtypes.MetadataDS, r repo.LockedRepo) (dtypes.ProviderDataTransfer, error) {
net := dtnet.NewFromLibp2pHost(h)
// NewProviderTransferNetwork sets up the libp2p2 protocol networking for data transfer
func NewProviderTransferNetwork(h host.Host) dtypes.ProviderTransferNetwork {
return dtnet.NewFromLibp2pHost(h)
}
// NewProviderTransport sets up a data transfer transport over graphsync
func NewProviderTransport(h host.Host, gs dtypes.StagingGraphsync, net dtypes.ProviderTransferNetwork) dtypes.ProviderTransport {
return dtgstransport.NewTransport(h.ID(), gs, net)
}
// NewProviderDataTransfer returns a data transfer manager
func NewProviderDataTransfer(lc fx.Lifecycle, net dtypes.ProviderTransferNetwork, transport dtypes.ProviderTransport, ds dtypes.MetadataDS, r repo.LockedRepo) (dtypes.ProviderDataTransfer, error) {
dtDs := namespace.Wrap(ds, datastore.NewKey("/datatransfer/provider/transfers"))
transport := dtgstransport.NewTransport(h.ID(), gs, net)
err := os.MkdirAll(filepath.Join(r.Path(), "data-transfer"), 0755) //nolint: gosec
if err != nil && !os.IsExist(err) {
return nil, err