Skip to content

Commit

Permalink
feat: persist limits to Swarm.ResourceMgr.Limits (#8901)
Browse files Browse the repository at this point in the history
* feat: persist limit changes to config

This changes the "ipfs swarm limit" command so that when limit changes
are applied via the command line, they are persisted to the repo
config, so that they remain in effect when the daemon restarts.

Any existing limit.json can be dropped into the IPFS config easily
using something like:

cat ~/.ipfs/config | jq ".Swarm.ResourceMgr.Limits = $(cat limit.json)" | sponge ~/.ipfs/config

This also upgrades to Resource Manager v0.3.0, which exports the config
schema so that we don't have to maintain our own copy of it.

Co-authored-by: Marcin Rataj <[email protected]>
  • Loading branch information
guseggert and lidel authored Apr 28, 2022
1 parent d4879a4 commit 74aff24
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 114 deletions.
46 changes: 4 additions & 42 deletions config/swarm.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package config

import rcmgr "github.com/libp2p/go-libp2p-resource-manager"

type SwarmConfig struct {
// AddrFilters specifies a set libp2p addresses that we should never
// dial or receive connections from.
Expand Down Expand Up @@ -137,10 +139,8 @@ type ConnMgr struct {
// <https://github.com/libp2p/go-libp2p-resource-manager#readme>
type ResourceMgr struct {
// Enables the Network Resource Manager feature
Enabled Flag `json:",omitempty"`

/* TODO: decide if and how we want to expose limits in our config
Limits *ResourceMgrScopeConfig `json:",omitempty"` */
Enabled Flag `json:",omitempty"`
Limits *rcmgr.BasicLimiterConfig `json:",omitempty"`
}

const (
Expand All @@ -150,41 +150,3 @@ const (
ResourceMgrProtocolScopePrefix = "proto:"
ResourceMgrPeerScopePrefix = "peer:"
)

/* TODO: decide if and how we want to expose limits in our config
type ResourceMgrLimitsConfig struct {
System *ResourceMgrScopeConfig `json:",omitempty"`
Transient *ResourceMgrScopeConfig `json:",omitempty"`
ServiceDefault *ResourceMgrScopeConfig `json:",omitempty"`
ServicePeerDefault *ResourceMgrScopeConfig `json:",omitempty"`
Service map[string]ResourceMgrScopeConfig `json:",omitempty"`
ServicePeer map[string]ResourceMgrScopeConfig `json:",omitempty"`
ProtocolDefault *ResourceMgrScopeConfig `json:",omitempty"`
ProtocolPeerDefault *ResourceMgrScopeConfig `json:",omitempty"`
Protocol map[string]ResourceMgrScopeConfig `json:",omitempty"`
ProtocolPeer map[string]ResourceMgrScopeConfig `json:",omitempty"`
PeerDefault *ResourceMgrScopeConfig `json:",omitempty"`
Peer map[string]ResourceMgrScopeConfig `json:",omitempty"`
Conn *ResourceMgrScopeConfig `json:",omitempty"`
Stream *ResourceMgrScopeConfig `json:",omitempty"`
}
*/

// libp2p Network Resource Manager config for a scope
type ResourceMgrScopeConfig struct {
Dynamic bool `json:",omitempty"`
// set if Dynamic is false
Memory int64 `json:",omitempty"`
// set if Dynamic is true
MemoryFraction float64 `json:",omitempty"`
MinMemory int64 `json:",omitempty"`
MaxMemory int64 `json:",omitempty"`

Streams, StreamsInbound, StreamsOutbound int
Conns, ConnsInbound, ConnsOutbound int
FD int
}
8 changes: 4 additions & 4 deletions core/commands/swarm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
cmds "github.com/ipfs/go-ipfs-cmds"
inet "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
rcmgr "github.com/libp2p/go-libp2p-resource-manager"
ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
mamask "github.com/whyrusleeping/multiaddr-filter"
Expand Down Expand Up @@ -380,8 +381,7 @@ It is possible to use this command to inspect and tweak limits at runtime:
$ vi limit.json
$ ipfs swarm limit system limit.json
Changes made via command line are discarded on node shutdown.
For permanent limits set Swarm.ResourceMgr.Limits in the $IPFS_PATH/config file.
Changes made via command line are persisted in the Swarm.ResourceMgr.Limits field of the $IPFS_PATH/config file.
`},
Arguments: []cmds.Argument{
cmds.StringArg("scope", true, false, "scope of the limit"),
Expand All @@ -401,7 +401,7 @@ For permanent limits set Swarm.ResourceMgr.Limits in the $IPFS_PATH/config file.

// set scope limit to new values (when limit.json is passed as a second arg)
if req.Files != nil {
var newLimit config.ResourceMgrScopeConfig
var newLimit rcmgr.BasicLimitConfig
it := req.Files.Entries()
if it.Next() {
file := files.FileFromEntry(it)
Expand All @@ -411,7 +411,7 @@ For permanent limits set Swarm.ResourceMgr.Limits in the $IPFS_PATH/config file.
if err := json.NewDecoder(file).Decode(&newLimit); err != nil {
return errors.New("failed to decode JSON as ResourceMgrScopeConfig")
}
return libp2p.NetSetLimit(node.ResourceManager, scope, newLimit)
return libp2p.NetSetLimit(node.ResourceManager, node.Repo, scope, newLimit)
}
if err := it.Err(); err != nil {
return fmt.Errorf("error opening limit JSON file: %w", err)
Expand Down
106 changes: 69 additions & 37 deletions core/node/libp2p/rcmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package libp2p

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
Expand All @@ -27,7 +26,6 @@ var NoResourceMgrError = fmt.Errorf("missing ResourceMgr: make sure the daemon i

func ResourceManager(cfg config.SwarmConfig) func(fx.Lifecycle, repo.Repo) (network.ResourceManager, Libp2pOpts, error) {
return func(lc fx.Lifecycle, repo repo.Repo) (network.ResourceManager, Libp2pOpts, error) {
var limiter *rcmgr.BasicLimiter
var manager network.ResourceManager
var opts Libp2pOpts

Expand All @@ -47,25 +45,18 @@ func ResourceManager(cfg config.SwarmConfig) func(fx.Lifecycle, repo.Repo) (netw

repoPath, err := config.PathRoot()
if err != nil {
return nil, opts, fmt.Errorf("error opening IPFS_PATH: %w", err)
return nil, opts, fmt.Errorf("opening IPFS_PATH: %w", err)
}

// Create limiter:
// - parse $IPFS_PATH/limits.json if exists
// - use defaultLimits from rcmgr_defaults.go
defaultLimits := adjustedDefaultLimits(cfg)
limitFilePath := filepath.Join(repoPath, NetLimitDefaultFilename)
limitFile, err := os.Open(limitFilePath)
switch {
case err == nil:
defer limitFile.Close()
limiter, err = rcmgr.NewLimiterFromJSON(limitFile, defaultLimits)
if err != nil {
return nil, opts, fmt.Errorf("error parsing libp2p limit file: %w", err)
}
case errors.Is(err, os.ErrNotExist):
limiter = rcmgr.NewStaticLimiter(defaultLimits)
default:

var limits rcmgr.BasicLimiterConfig
if cfg.ResourceMgr.Limits != nil {
limits = *cfg.ResourceMgr.Limits
}

limiter, err := rcmgr.NewLimiter(limits, defaultLimits)
if err != nil {
return nil, opts, err
}

Expand All @@ -80,9 +71,8 @@ func ResourceManager(cfg config.SwarmConfig) func(fx.Lifecycle, repo.Repo) (netw

manager, err = rcmgr.NewResourceManager(limiter, ropts...)
if err != nil {
return nil, opts, fmt.Errorf("error creating libp2p resource manager: %w", err)
return nil, opts, fmt.Errorf("creating libp2p resource manager: %w", err)
}

} else {
log.Debug("libp2p resource manager is disabled")
manager = network.NullResourceManager
Expand Down Expand Up @@ -196,14 +186,13 @@ func NetStat(mgr network.ResourceManager, scope string) (NetStatOut, error) {
}
}

func NetLimit(mgr network.ResourceManager, scope string) (config.ResourceMgrScopeConfig, error) {
var result config.ResourceMgrScopeConfig
func NetLimit(mgr network.ResourceManager, scope string) (rcmgr.BasicLimitConfig, error) {
var result rcmgr.BasicLimitConfig
getLimit := func(s network.ResourceScope) error {
limiter, ok := s.(rcmgr.ResourceScopeLimiter)
if !ok { // NullResourceManager
return NoResourceMgrError
}

limit := limiter.Limit()
switch l := limit.(type) {
case *rcmgr.StaticLimit:
Expand Down Expand Up @@ -280,7 +269,8 @@ func NetLimit(mgr network.ResourceManager, scope string) (config.ResourceMgrScop
}
}

func NetSetLimit(mgr network.ResourceManager, scope string, limit config.ResourceMgrScopeConfig) error {
// NetSetLimit sets new ResourceManager limits for the given scope. The limits take effect immediately, and are also persisted to the repo config.
func NetSetLimit(mgr network.ResourceManager, repo repo.Repo, scope string, limit rcmgr.BasicLimitConfig) error {
setLimit := func(s network.ResourceScope) error {
limiter, ok := s.(rcmgr.ResourceScopeLimiter)
if !ok { // NullResourceManager
Expand Down Expand Up @@ -324,45 +314,87 @@ func NetSetLimit(mgr network.ResourceManager, scope string, limit config.Resourc
return nil
}

cfg, err := repo.Config()
if err != nil {
return fmt.Errorf("reading config to set limit: %w", err)
}

if cfg.Swarm.ResourceMgr.Limits == nil {
cfg.Swarm.ResourceMgr.Limits = &rcmgr.BasicLimiterConfig{}
}
configLimits := cfg.Swarm.ResourceMgr.Limits

var setConfigFunc func()
switch {
case scope == config.ResourceMgrSystemScope:
err := mgr.ViewSystem(func(s network.ResourceScope) error {
err = mgr.ViewSystem(func(s network.ResourceScope) error {
return setLimit(s)
})
return err
setConfigFunc = func() { configLimits.System = &limit }

case scope == config.ResourceMgrTransientScope:
err := mgr.ViewTransient(func(s network.ResourceScope) error {
err = mgr.ViewTransient(func(s network.ResourceScope) error {
return setLimit(s)
})
return err
setConfigFunc = func() { configLimits.Transient = &limit }

case strings.HasPrefix(scope, config.ResourceMgrServiceScopePrefix):
svc := scope[4:]
err := mgr.ViewService(svc, func(s network.ServiceScope) error {
svc := strings.TrimPrefix(scope, config.ResourceMgrServiceScopePrefix)
err = mgr.ViewService(svc, func(s network.ServiceScope) error {
return setLimit(s)
})
return err
setConfigFunc = func() {
if configLimits.Service == nil {
configLimits.Service = map[string]rcmgr.BasicLimitConfig{}
}
configLimits.Service[svc] = limit
}

case strings.HasPrefix(scope, config.ResourceMgrProtocolScopePrefix):
proto := scope[6:]
err := mgr.ViewProtocol(protocol.ID(proto), func(s network.ProtocolScope) error {
proto := strings.TrimPrefix(scope, config.ResourceMgrProtocolScopePrefix)
err = mgr.ViewProtocol(protocol.ID(proto), func(s network.ProtocolScope) error {
return setLimit(s)
})
return err
setConfigFunc = func() {
if configLimits.Protocol == nil {
configLimits.Protocol = map[string]rcmgr.BasicLimitConfig{}
}
configLimits.Protocol[proto] = limit
}

case strings.HasPrefix(scope, config.ResourceMgrPeerScopePrefix):
p := scope[5:]
pid, err := peer.Decode(p)
p := strings.TrimPrefix(scope, config.ResourceMgrPeerScopePrefix)
var pid peer.ID
pid, err = peer.Decode(p)
if err != nil {
return fmt.Errorf("invalid peer ID: %q: %w", p, err)
}
err = mgr.ViewPeer(pid, func(s network.PeerScope) error {
return setLimit(s)
})
return err
setConfigFunc = func() {
if configLimits.Peer == nil {
configLimits.Peer = map[string]rcmgr.BasicLimitConfig{}
}
configLimits.Peer[p] = limit
}

default:
return fmt.Errorf("invalid scope %q", scope)
}

if err != nil {
return fmt.Errorf("setting new limits on resource manager: %w", err)
}

if cfg.Swarm.ResourceMgr.Limits == nil {
cfg.Swarm.ResourceMgr.Limits = &rcmgr.BasicLimiterConfig{}
}
setConfigFunc()

if err := repo.SetConfig(cfg); err != nil {
return fmt.Errorf("writing new limits to repo config: %w", err)
}

return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ require (
github.com/libp2p/go-libp2p-pubsub-router v0.5.0
github.com/libp2p/go-libp2p-quic-transport v0.16.1
github.com/libp2p/go-libp2p-record v0.1.3
github.com/libp2p/go-libp2p-resource-manager v0.1.5
github.com/libp2p/go-libp2p-resource-manager v0.3.0
github.com/libp2p/go-libp2p-routing-helpers v0.2.3
github.com/libp2p/go-libp2p-swarm v0.10.2
github.com/libp2p/go-libp2p-testing v0.8.0
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -910,8 +910,9 @@ github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7
github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk=
github.com/libp2p/go-libp2p-record v0.1.3 h1:R27hoScIhQf/A8XJZ8lYpnqh9LatJ5YbHs28kCIfql0=
github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4=
github.com/libp2p/go-libp2p-resource-manager v0.1.5 h1:7J6t9KLFS0MxXDTfqA6rwfVCZl/yLQnXW5LpZjHAANI=
github.com/libp2p/go-libp2p-resource-manager v0.1.5/go.mod h1:wJPNjeE4XQlxeidwqVY5G6DLOKqFK33u2n8blpl0I6Y=
github.com/libp2p/go-libp2p-resource-manager v0.3.0 h1:2+cYxUNi33tcydsVLt6K5Fv2E3OTiVeafltecAj15E0=
github.com/libp2p/go-libp2p-resource-manager v0.3.0/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ=
github.com/libp2p/go-libp2p-routing v0.0.1/go.mod h1:N51q3yTr4Zdr7V8Jt2JIktVU+3xBBylx1MZeVA6t1Ys=
github.com/libp2p/go-libp2p-routing-helpers v0.2.3 h1:xY61alxJ6PurSi+MXbywZpelvuU4U4p/gPTxjqCqTzY=
github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw=
Expand Down
Loading

0 comments on commit 74aff24

Please sign in to comment.