feat: Reduce RM code footprint
Co-Authored-By: Antonio Navarro Perez <antnavper@gmail.com>
Jorropo committed
Mar 1, 2023 at 15:29 UTC
7986196414f37f79d35cd4183af8d56a53b9ec48
19 files changed
+644
-956
.circleci/main.yml
+1
-1
@@ -309,7 +309,7 @@ jobs:
309
- run:
310
name: Cloning
311
command: |
312
- git clone https://github.com/ipfs/go-ipfs-http-client.git
312
+ git clone https://github.com/ipfs/go-ipfs-http-client.git -b bump-for-rcmgr-last-push
313
git -C go-ipfs-http-client log -1
314
- restore_cache:
315
keys:
.github/workflows/build.yml
+1
@@ -143,6 +143,7 @@ jobs:
143
with:
144
repository: ipfs/go-ipfs-http-client
145
path: go-ipfs-http-client
146
+ ref: bump-for-rcmgr-last-push
147
- uses: protocol/cache-go-action@v1
148
with:
149
name: ${{ github.job }}
config/swarm.go
+2
-4
@@ -1,7 +1,5 @@
1
package config
2
3
-import rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
4
-
3
type SwarmConfig struct {
4
// AddrFilters specifies a set libp2p addresses that we should never
5
// dial or receive connections from.
@@ -141,8 +139,8 @@ type ConnMgr struct {
139
// <https://github.com/libp2p/go-libp2p/tree/master/p2p/host/resource-manager#readme>
140
type ResourceMgr struct {
141
// Enables the Network Resource Manager feature, default to on.
144
- Enabled Flag `json:",omitempty"`
145
- Limits *rcmgr.PartialLimitConfig `json:",omitempty"`
142
+ Enabled Flag `json:",omitempty"`
143
+ Limits swarmLimits `json:",omitempty"`
144
145
MaxMemory *OptionalString `json:",omitempty"`
146
MaxFileDescriptors *OptionalInteger `json:",omitempty"`
config/types.go
+26
@@ -1,8 +1,10 @@
1
package config
2
3
import (
4
+ "bytes"
5
"encoding/json"
6
"fmt"
7
+ "io"
8
"strings"
9
"time"
10
)
@@ -412,3 +414,27 @@ func (p OptionalString) String() string {
414
415
var _ json.Unmarshaler = (*OptionalInteger)(nil)
416
var _ json.Marshaler = (*OptionalInteger)(nil)
417
+
418
+type swarmLimits struct{}
419
+
420
+var _ json.Unmarshaler = swarmLimits{}
421
+
422
+func (swarmLimits) UnmarshalJSON(b []byte) error {
423
+ d := json.NewDecoder(bytes.NewReader(b))
424
+ for {
425
+ switch tok, err := d.Token(); err {
426
+ case io.EOF:
427
+ return nil
428
+ case nil:
429
+ switch tok {
430
+ case json.Delim('{'), json.Delim('}'):
431
+ // accept empty objects
432
+ continue
433
+ }
434
+ //nolint
435
+ return fmt.Errorf("The Swarm.ResourceMgr.Limits configuration has been removed in Kubo 0.19 and should be empty or not present. To set custom libp2p limits, read https://github.com/ipfs/kubo/blob/master/docs/libp2p-resource-management.md#user-supplied-override-limits")
436
+ default:
437
+ return err
438
+ }
439
+ }
440
+}
core/commands/commands_test.go
+1
-2
@@ -248,13 +248,12 @@ func TestCommands(t *testing.T) {
248
"/swarm/filters",
249
"/swarm/filters/add",
250
"/swarm/filters/rm",
251
- "/swarm/limit",
251
"/swarm/peers",
252
"/swarm/peering",
253
"/swarm/peering/add",
254
"/swarm/peering/ls",
255
"/swarm/peering/rm",
257
- "/swarm/stats",
256
+ "/swarm/resources",
257
"/tar",
258
"/tar/add",
259
"/tar/cat",
core/commands/swarm.go
+53
-128
@@ -1,7 +1,6 @@
1
package commands
2
3
import (
4
- "bytes"
4
"context"
5
"encoding/json"
6
"errors"
@@ -9,10 +8,11 @@ import (
8
"io"
9
"path"
10
"sort"
11
+ "strconv"
12
"sync"
13
+ "text/tabwriter"
14
"time"
15
15
- "github.com/ipfs/go-libipfs/files"
16
"github.com/ipfs/kubo/commands"
17
"github.com/ipfs/kubo/config"
18
"github.com/ipfs/kubo/core/commands/cmdenv"
@@ -57,8 +57,8 @@ ipfs peers in the internet.
57
"filters": swarmFiltersCmd,
58
"peers": swarmPeersCmd,
59
"peering": swarmPeeringCmd,
60
- "stats": swarmStatsCmd, // libp2p Network Resource Manager
61
- "limit": swarmLimitCmd, // libp2p Network Resource Manager
60
+ "resources": swarmResourcesCmd, // libp2p Network Resource Manager
61
+
62
},
63
}
64
@@ -323,30 +323,15 @@ var swarmPeersCmd = &cmds.Command{
323
Type: connInfos{},
324
}
325
326
-var swarmStatsCmd = &cmds.Command{
326
+var swarmResourcesCmd = &cmds.Command{
327
Status: cmds.Experimental,
328
Helptext: cmds.HelpText{
329
- Tagline: "Report resource usage for a scope.",
330
- LongDescription: `Report resource usage for a scope.
331
-The scope can be one of the following:
332
-- system -- reports the system aggregate resource usage.
333
-- transient -- reports the transient resource usage.
334
-- svc:<service> -- reports the resource usage of a specific service.
335
-- proto:<proto> -- reports the resource usage of a specific protocol.
336
-- peer:<peer> -- reports the resource usage of a specific peer.
337
-- all -- reports the resource usage for all currently active scopes.
338
-
339
-The output of this command is JSON.
340
-
341
-To see all resources that are close to hitting their respective limit, one can do something like:
342
- ipfs swarm stats --min-used-limit-perc=90 all
329
+ Tagline: "Get a summary of all resources accounted for by the libp2p Resource Manager.",
330
+ LongDescription: `
331
+Get a summary of all resources accounted for by the libp2p Resource Manager.
332
+This includes the limits and the usage against those limits.
333
+This can output a human readable table and JSON encoding.
334
`},
344
- Arguments: []cmds.Argument{
345
- cmds.StringArg("scope", true, false, "scope of the stat report"),
346
- },
347
- Options: []cmds.Option{
348
- cmds.IntOption(swarmUsedResourcesPercentageName, "Only display resources that are using above the specified percentage of their respective limit"),
349
- },
335
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
336
node, err := cmdenv.GetNode(env)
337
if err != nil {
@@ -357,128 +342,68 @@ To see all resources that are close to hitting their respective limit, one can d
342
return libp2p.ErrNoResourceMgr
343
}
344
360
- if len(req.Arguments) != 1 {
361
- return fmt.Errorf("must specify exactly one scope")
362
- }
363
-
364
- percentage, _ := req.Options[swarmUsedResourcesPercentageName].(int)
365
- scope := req.Arguments[0]
366
-
367
- if percentage != 0 && scope != "all" {
368
- return fmt.Errorf("%q can only be used when scope is %q", swarmUsedResourcesPercentageName, "all")
369
- }
370
-
371
- result, err := libp2p.NetStat(node.ResourceManager, scope, percentage)
345
+ cfg, err := node.Repo.Config()
346
if err != nil {
347
return err
348
}
349
376
- b := new(bytes.Buffer)
377
- enc := json.NewEncoder(b)
378
- err = enc.Encode(result)
350
+ userResourceOverrides, err := node.Repo.UserResourceOverrides()
351
if err != nil {
352
return err
353
}
382
- return cmds.EmitOnce(res, b)
383
- },
384
- Encoders: cmds.EncoderMap{
385
- cmds.Text: HumanJSONEncoder,
386
- },
387
-}
388
-
389
-var swarmLimitCmd = &cmds.Command{
390
- Status: cmds.Experimental,
391
- Helptext: cmds.HelpText{
392
- Tagline: "Get or set resource limits for a scope.",
393
- LongDescription: `Get or set resource limits for a scope.
394
-The scope can be one of the following:
395
-- all -- all limits actually being applied.
396
-- system -- limits for the system aggregate resource usage.
397
-- transient -- limits for the transient resource usage.
398
-- svc:<service> -- limits for the resource usage of a specific service.
399
-- proto:<proto> -- limits for the resource usage of a specific protocol.
400
-- peer:<peer> -- limits for the resource usage of a specific peer.
401
-
402
-The output of this command is JSON.
403
-
404
-It is possible to use this command to inspect and tweak limits at runtime:
405
-
406
- $ ipfs swarm limit system > limit.json
407
- $ vi limit.json
408
- $ ipfs swarm limit system limit.json
354
410
-Changes made via command line are persisted in the Swarm.ResourceMgr.Limits field of the $IPFS_PATH/config file.
411
-`},
412
- Arguments: []cmds.Argument{
413
- cmds.StringArg("scope", true, false, "scope of the limit"),
414
- cmds.FileArg("limit.json", false, false, "limits to be set").EnableStdin(),
415
- },
416
- Options: []cmds.Option{
417
- cmds.BoolOption(swarmResetLimitsOptionName, "reset limit to default"),
418
- },
419
- Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
420
- node, err := cmdenv.GetNode(env)
355
+ // FIXME: we shouldn't recompute limits, either save them or load them from libp2p (https://github.com/libp2p/go-libp2p/issues/2166)
356
+ limitConfig, _, err := libp2p.LimitConfig(cfg.Swarm, userResourceOverrides)
357
if err != nil {
358
return err
359
}
360
425
- if node.ResourceManager == nil {
361
+ rapi, ok := node.ResourceManager.(rcmgr.ResourceManagerState)
362
+ if !ok { // NullResourceManager
363
return libp2p.ErrNoResourceMgr
364
}
365
429
- scope := req.Arguments[0]
430
-
431
- // set scope limit to new values (when limit.json is passed as a second arg)
432
- if req.Files != nil {
433
- var newLimit rcmgr.ResourceLimits
434
- it := req.Files.Entries()
435
- if it.Next() {
436
- file := files.FileFromEntry(it)
437
- if file == nil {
438
- return errors.New("expected a JSON file")
439
- }
440
-
441
- r := io.LimitReader(file, 32*1024*1024) // 32MiB
442
-
443
- if err := json.NewDecoder(r).Decode(&newLimit); err != nil {
444
- return fmt.Errorf("decoding JSON as ResourceMgrScopeConfig: %w", err)
366
+ return cmds.EmitOnce(res, libp2p.MergeLimitsAndStatsIntoLimitsConfigAndUsage(limitConfig, rapi.Stat()))
367
+ },
368
+ Encoders: cmds.EncoderMap{
369
+ cmds.JSON: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, limitsAndUsage libp2p.LimitsConfigAndUsage) error {
370
+ return json.NewEncoder(w).Encode(limitsAndUsage)
371
+ }),
372
+ cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, limitsAndUsage libp2p.LimitsConfigAndUsage) error {
373
+ tw := tabwriter.NewWriter(w, 20, 8, 0, '\t', 0)
374
+ defer tw.Flush()
375
+
376
+ fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t\n", "Scope", "Limit Name", "Limit Value", "Limit Usage Amount", "Limit Usage Percent")
377
+ for _, ri := range libp2p.LimitConfigsToInfo(limitsAndUsage) {
378
+ var limit, percentage string
379
+ switch ri.LimitValue {
380
+ case rcmgr.Unlimited64:
381
+ limit = "unlimited"
382
+ percentage = "n/a"
383
+ case rcmgr.BlockAllLimit64:
384
+ limit = "blockAll"
385
+ percentage = "n/a"
386
+ default:
387
+ limit = strconv.FormatInt(int64(ri.LimitValue), 10)
388
+ if ri.CurrentUsage == 0 {
389
+ percentage = "0%"
390
+ } else {
391
+ percentage = strconv.FormatFloat(float64(ri.CurrentUsage)/float64(ri.LimitValue)*100, 'f', 1, 64) + "%"
392
+ }
393
}
446
- return libp2p.NetSetLimit(node.ResourceManager, node.Repo, scope, newLimit)
394
+ fmt.Fprintf(tw, "%s\t%s\t%s\t%d\t%s\t\n",
395
+ ri.ScopeName,
396
+ ri.LimitName,
397
+ limit,
398
+ ri.CurrentUsage,
399
+ percentage,
400
+ )
401
}
448
- if err := it.Err(); err != nil {
449
- return fmt.Errorf("error opening limit JSON file: %w", err)
450
- }
451
- }
452
-
453
- var result interface{}
454
- switch _, reset := req.Options[swarmResetLimitsOptionName]; {
455
- case reset:
456
- result, err = libp2p.NetResetLimit(node.ResourceManager, node.Repo, scope)
457
- case scope == "all":
458
- result, err = libp2p.NetLimitAll(node.ResourceManager)
459
- default:
460
- // get scope limit
461
- result, err = libp2p.NetLimit(node.ResourceManager, scope)
462
- }
463
- if err != nil {
464
- return err
465
- }
402
467
- if base, ok := result.(rcmgr.BaseLimit); ok {
468
- result = base.ToResourceLimits()
469
- }
470
-
471
- b := new(bytes.Buffer)
472
- enc := json.NewEncoder(b)
473
- err = enc.Encode(result)
474
- if err != nil {
475
- return err
476
- }
477
- return cmds.EmitOnce(res, b)
478
- },
479
- Encoders: cmds.EncoderMap{
480
- cmds.Text: HumanJSONEncoder,
403
+ return nil
404
+ }),
405
},
406
+ Type: libp2p.LimitsConfigAndUsage{},
407
}
408
409
type streamInfo struct {
core/node/groups.go
+18
-15
@@ -6,21 +6,19 @@ import (
6
"fmt"
7
"time"
8
9
+ "github.com/dustin/go-humanize"
10
blockstore "github.com/ipfs/go-ipfs-blockstore"
11
+ offline "github.com/ipfs/go-ipfs-exchange-offline"
12
util "github.com/ipfs/go-ipfs-util"
13
"github.com/ipfs/go-log"
14
+ uio "github.com/ipfs/go-unixfs/io"
15
"github.com/ipfs/kubo/config"
16
+ "github.com/ipfs/kubo/core/node/libp2p"
17
+ "github.com/ipfs/kubo/p2p"
18
pubsub "github.com/libp2p/go-libp2p-pubsub"
19
"github.com/libp2p/go-libp2p-pubsub/timecache"
20
"github.com/libp2p/go-libp2p/core/peer"
16
-
17
- "github.com/ipfs/kubo/core/node/libp2p"
18
- "github.com/ipfs/kubo/p2p"
19
-
20
- offline "github.com/ipfs/go-ipfs-exchange-offline"
21
- uio "github.com/ipfs/go-unixfs/io"
22
-
23
- "github.com/dustin/go-humanize"
21
+ rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
22
"go.uber.org/fx"
23
)
24
@@ -37,7 +35,7 @@ var BaseLibP2P = fx.Options(
35
fx.Invoke(libp2p.PNetChecker),
36
)
37
40
-func LibP2P(bcfg *BuildCfg, cfg *config.Config) fx.Option {
38
+func LibP2P(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.PartialLimitConfig) fx.Option {
39
var connmgr fx.Option
40
41
// set connmgr based on Swarm.ConnMgr.Type
@@ -150,7 +148,7 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config) fx.Option {
148
fx.Provide(libp2p.UserAgent()),
149
150
// Services (resource management)
153
- fx.Provide(libp2p.ResourceManager(cfg.Swarm)),
151
+ fx.Provide(libp2p.ResourceManager(cfg.Swarm, userResourceOverrides)),
152
fx.Provide(libp2p.AddrFilters(cfg.Swarm.AddrFilters)),
153
fx.Provide(libp2p.AddrsFactory(cfg.Addresses.Announce, cfg.Addresses.AppendAnnounce, cfg.Addresses.NoAnnounce)),
154
fx.Provide(libp2p.SmuxTransport(cfg.Swarm.Transports)),
@@ -249,7 +247,7 @@ var IPNS = fx.Options(
247
)
248
249
// Online groups online-only units
252
-func Online(bcfg *BuildCfg, cfg *config.Config) fx.Option {
250
+func Online(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.PartialLimitConfig) fx.Option {
251
252
// Namesys params
253
@@ -303,7 +301,7 @@ func Online(bcfg *BuildCfg, cfg *config.Config) fx.Option {
301
302
fx.Provide(p2p.New),
303
306
- LibP2P(bcfg, cfg),
304
+ LibP2P(bcfg, cfg, userResourceOverrides),
305
OnlineProviders(
306
cfg.Experimental.StrategicProviding,
307
cfg.Experimental.AcceleratedDHTClient,
@@ -340,9 +338,9 @@ var Core = fx.Options(
338
fx.Provide(Files),
339
)
340
343
-func Networked(bcfg *BuildCfg, cfg *config.Config) fx.Option {
341
+func Networked(bcfg *BuildCfg, cfg *config.Config, userResourceOverrides rcmgr.PartialLimitConfig) fx.Option {
342
if bcfg.Online {
345
- return Online(bcfg, cfg)
343
+ return Online(bcfg, cfg, userResourceOverrides)
344
}
345
return Offline(cfg)
346
}
@@ -358,6 +356,11 @@ func IPFS(ctx context.Context, bcfg *BuildCfg) fx.Option {
356
return bcfgOpts // error
357
}
358
359
+ userResourceOverrides, err := bcfg.Repo.UserResourceOverrides()
360
+ if err != nil {
361
+ return fx.Error(err)
362
+ }
363
+
364
// Auto-sharding settings
365
shardSizeString := cfg.Internal.UnixFSShardingSizeThreshold.WithDefault("256kiB")
366
shardSizeInt, err := humanize.ParseBytes(shardSizeString)
@@ -381,7 +384,7 @@ func IPFS(ctx context.Context, bcfg *BuildCfg) fx.Option {
384
Storage(bcfg, cfg),
385
Identity(cfg),
386
IPNS,
384
- Networked(bcfg, cfg),
387
+ Networked(bcfg, cfg, userResourceOverrides),
388
389
Core,
390
)
core/node/libp2p/rcmgr.go
+302
-488
@@ -2,10 +2,10 @@ package libp2p
2
3
import (
4
"context"
5
+ "encoding/json"
6
"fmt"
7
"os"
8
"path/filepath"
8
- "strings"
9
10
"github.com/benbjohnson/clock"
11
logging "github.com/ipfs/go-log/v2"
@@ -17,21 +17,17 @@ import (
17
rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager/obs"
18
"github.com/multiformats/go-multiaddr"
19
"go.uber.org/fx"
20
- "golang.org/x/exp/constraints"
20
21
"github.com/ipfs/kubo/config"
22
"github.com/ipfs/kubo/core/node/helpers"
23
"github.com/ipfs/kubo/repo"
24
)
25
27
-// FIXME(@Jorropo): for go-libp2p v0.26.0 use .MustConcrete and .MustBaseLimit instead of .Build(rcmgr.BaseLimit{}).
28
-
29
-const NetLimitDefaultFilename = "limit.json"
26
const NetLimitTraceFilename = "rcmgr.json.gz"
27
28
var ErrNoResourceMgr = fmt.Errorf("missing ResourceMgr: make sure the daemon is running with Swarm.ResourceMgr.Enabled")
29
34
-func ResourceManager(cfg config.SwarmConfig) interface{} {
30
+func ResourceManager(cfg config.SwarmConfig, userResourceOverrides rcmgr.PartialLimitConfig) interface{} {
31
return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo repo.Repo) (network.ResourceManager, Libp2pOpts, error) {
32
var manager network.ResourceManager
33
var opts Libp2pOpts
@@ -54,32 +50,25 @@ func ResourceManager(cfg config.SwarmConfig) interface{} {
50
return nil, opts, fmt.Errorf("opening IPFS_PATH: %w", err)
51
}
52
57
- var limitConfig rcmgr.ConcreteLimitConfig
58
- defaultComputedLimitConfig, err := createDefaultLimitConfig(cfg)
53
+ limitConfig, msg, err := LimitConfig(cfg, userResourceOverrides)
54
if err != nil {
60
- return nil, opts, err
55
+ return nil, opts, fmt.Errorf("creating final Resource Manager config: %w", err)
56
}
57
63
- // The logic for defaults and overriding with specified SwarmConfig.ResourceMgr.Limits
64
- // is documented in docs/config.md.
65
- // Any changes here should be reflected there.
66
- if cfg.ResourceMgr.Limits != nil {
67
- userSuppliedOverrideLimitConfig := *cfg.ResourceMgr.Limits
68
- // This effectively overrides the computed default LimitConfig with any non-zero values from cfg.ResourceMgr.Limits.
69
- // Because of how how Apply works, any 0 value for a user supplied override
70
- // will be overriden with a computed default value.
71
- // There currently isn't a way for a user to supply a 0-value override.
72
- limitConfig = userSuppliedOverrideLimitConfig.Build(defaultComputedLimitConfig)
73
- } else {
74
- limitConfig = defaultComputedLimitConfig
58
+ if !isPartialConfigEmpty(userResourceOverrides) {
59
+ fmt.Print(`
60
+libp2p-resource-limit-overrides.json has been loaded, "default" fields will be
61
+filled in with autocomputed defaults.
62
+`)
63
}
64
77
- if err := ensureConnMgrMakeSenseVsResourceMgr(limitConfig, cfg.ConnMgr); err != nil {
65
+ // We want to see this message on startup, that's why we are using fmt instead of log.
66
+ fmt.Print(msg)
67
+
68
+ if err := ensureConnMgrMakeSenseVsResourceMgr(limitConfig, cfg); err != nil {
69
return nil, opts, err
70
}
71
81
- limiter := rcmgr.NewFixedLimiter(limitConfig)
82
-
72
str, err := rcmgrObs.NewStatsTraceReporter()
73
if err != nil {
74
return nil, opts, err
@@ -106,6 +95,8 @@ func ResourceManager(cfg config.SwarmConfig) interface{} {
95
ropts = append(ropts, rcmgr.WithTrace(traceFilePath))
96
}
97
98
+ limiter := rcmgr.NewFixedLimiter(limitConfig)
99
+
100
manager, err = rcmgr.NewResourceManager(limiter, ropts...)
101
if err != nil {
102
return nil, opts, fmt.Errorf("creating libp2p resource manager: %w", err)
@@ -133,540 +124,363 @@ func ResourceManager(cfg config.SwarmConfig) interface{} {
124
}
125
}
126
136
-type notOmitEmptyResourceLimit struct {
137
- Streams rcmgr.LimitVal
138
- StreamsInbound rcmgr.LimitVal
139
- StreamsOutbound rcmgr.LimitVal
140
- Conns rcmgr.LimitVal
141
- ConnsInbound rcmgr.LimitVal
142
- ConnsOutbound rcmgr.LimitVal
143
- FD rcmgr.LimitVal
144
- Memory rcmgr.LimitVal64
145
-}
146
-
147
-func resourceLimitsToNotOmitEmpty(r rcmgr.ResourceLimits) notOmitEmptyResourceLimit {
148
- return notOmitEmptyResourceLimit{
149
- Streams: r.Streams,
150
- StreamsInbound: r.StreamsInbound,
151
- StreamsOutbound: r.StreamsOutbound,
152
- Conns: r.Conns,
153
- ConnsInbound: r.ConnsInbound,
154
- ConnsOutbound: r.ConnsOutbound,
155
- FD: r.FD,
156
- Memory: r.Memory,
127
+func isPartialConfigEmpty(cfg rcmgr.PartialLimitConfig) bool {
128
+ var emptyResourceConfig rcmgr.ResourceLimits
129
+ if cfg.System != emptyResourceConfig ||
130
+ cfg.Transient != emptyResourceConfig ||
131
+ cfg.AllowlistedSystem != emptyResourceConfig ||
132
+ cfg.AllowlistedTransient != emptyResourceConfig ||
133
+ cfg.ServiceDefault != emptyResourceConfig ||
134
+ cfg.ServicePeerDefault != emptyResourceConfig ||
135
+ cfg.ProtocolDefault != emptyResourceConfig ||
136
+ cfg.ProtocolPeerDefault != emptyResourceConfig ||
137
+ cfg.PeerDefault != emptyResourceConfig ||
138
+ cfg.Conn != emptyResourceConfig ||
139
+ cfg.Stream != emptyResourceConfig {
140
+ return false
141
}
158
-}
159
-
160
-type NetStatOut struct {
161
- System *notOmitEmptyResourceLimit `json:",omitempty"`
162
- Transient *notOmitEmptyResourceLimit `json:",omitempty"`
163
- Services map[string]notOmitEmptyResourceLimit `json:",omitempty"`
164
- Protocols map[string]notOmitEmptyResourceLimit `json:",omitempty"`
165
- Peers map[string]notOmitEmptyResourceLimit `json:",omitempty"`
166
-}
167
-
168
-func NetStat(mgr network.ResourceManager, scope string, percentage int) (NetStatOut, error) {
169
- var err error
170
- var result NetStatOut
171
- switch {
172
- case scope == "all":
173
- rapi, ok := mgr.(rcmgr.ResourceManagerState)
174
- if !ok { // NullResourceManager
175
- return result, ErrNoResourceMgr
176
- }
177
-
178
- limits, err := NetLimitAll(mgr)
179
- if err != nil {
180
- return result, err
142
+ for _, v := range cfg.Service {
143
+ if v != emptyResourceConfig {
144
+ return false
145
}
182
-
183
- stat := rapi.Stat()
184
- if s := scopeToLimit(stat.System); compareLimits(s, *limits.System, percentage) {
185
- result.System = &s
186
- }
187
- if s := scopeToLimit(stat.Transient); compareLimits(s, *limits.Transient, percentage) {
188
- result.Transient = &s
189
- }
190
- if len(stat.Services) > 0 {
191
- result.Services = make(map[string]notOmitEmptyResourceLimit, len(stat.Services))
192
- for srv, s := range stat.Services {
193
- ls := limits.Services[srv]
194
- if stat := scopeToLimit(s); compareLimits(stat, ls, percentage) {
195
- result.Services[srv] = stat
196
- }
197
- }
198
- }
199
- if len(stat.Protocols) > 0 {
200
- result.Protocols = make(map[string]notOmitEmptyResourceLimit, len(stat.Protocols))
201
- for proto, s := range stat.Protocols {
202
- ls := limits.Protocols[string(proto)]
203
- if stat := scopeToLimit(s); compareLimits(stat, ls, percentage) {
204
- result.Protocols[string(proto)] = stat
205
- }
206
- }
146
+ }
147
+ for _, v := range cfg.ServicePeer {
148
+ if v != emptyResourceConfig {
149
+ return false
150
}
208
- if len(stat.Peers) > 0 {
209
- result.Peers = make(map[string]notOmitEmptyResourceLimit, len(stat.Peers))
210
- for p, s := range stat.Peers {
211
- ls := limits.Peers[p.Pretty()]
212
- if stat := scopeToLimit(s); compareLimits(stat, ls, percentage) {
213
- result.Peers[p.Pretty()] = stat
214
- }
215
- }
151
+ }
152
+ for _, v := range cfg.Protocol {
153
+ if v != emptyResourceConfig {
154
+ return false
155
}
217
-
218
- return result, nil
219
-
220
- case scope == config.ResourceMgrSystemScope:
221
- err = mgr.ViewSystem(func(s network.ResourceScope) error {
222
- stat := scopeToLimit(s.Stat())
223
- result.System = &stat
224
- return nil
225
- })
226
- return result, err
227
-
228
- case scope == config.ResourceMgrTransientScope:
229
- err = mgr.ViewTransient(func(s network.ResourceScope) error {
230
- stat := scopeToLimit(s.Stat())
231
- result.Transient = &stat
232
- return nil
233
- })
234
- return result, err
235
-
236
- case strings.HasPrefix(scope, config.ResourceMgrServiceScopePrefix):
237
- svc := strings.TrimPrefix(scope, config.ResourceMgrServiceScopePrefix)
238
- err = mgr.ViewService(svc, func(s network.ServiceScope) error {
239
- result.Services = map[string]notOmitEmptyResourceLimit{
240
- svc: scopeToLimit(s.Stat()),
241
- }
242
- return nil
243
- })
244
- return result, err
245
-
246
- case strings.HasPrefix(scope, config.ResourceMgrProtocolScopePrefix):
247
- proto := strings.TrimPrefix(scope, config.ResourceMgrProtocolScopePrefix)
248
- err = mgr.ViewProtocol(protocol.ID(proto), func(s network.ProtocolScope) error {
249
- result.Protocols = map[string]notOmitEmptyResourceLimit{
250
- proto: scopeToLimit(s.Stat()),
251
- }
252
- return nil
253
- })
254
- return result, err
255
-
256
- case strings.HasPrefix(scope, config.ResourceMgrPeerScopePrefix):
257
- p := strings.TrimPrefix(scope, config.ResourceMgrPeerScopePrefix)
258
- pid, err := peer.Decode(p)
259
- if err != nil {
260
- return result, fmt.Errorf("invalid peer ID: %q: %w", p, err)
156
+ }
157
+ for _, v := range cfg.ProtocolPeer {
158
+ if v != emptyResourceConfig {
159
+ return false
160
}
262
- err = mgr.ViewPeer(pid, func(s network.PeerScope) error {
263
- result.Peers = map[string]notOmitEmptyResourceLimit{
264
- p: scopeToLimit(s.Stat()),
265
- }
266
- return nil
267
- })
268
- return result, err
269
-
270
- default:
271
- return result, fmt.Errorf("invalid scope %q", scope)
161
}
273
-}
274
-
275
-var scopes = []string{
276
- config.ResourceMgrSystemScope,
277
- config.ResourceMgrTransientScope,
278
- config.ResourceMgrServiceScopePrefix,
279
- config.ResourceMgrProtocolScopePrefix,
280
- config.ResourceMgrPeerScopePrefix,
281
-}
282
-
283
-func scopeToLimit(s network.ScopeStat) notOmitEmptyResourceLimit {
284
- return notOmitEmptyResourceLimit{
285
- Streams: rcmgr.LimitVal(s.NumStreamsInbound + s.NumStreamsOutbound),
286
- StreamsInbound: rcmgr.LimitVal(s.NumStreamsInbound),
287
- StreamsOutbound: rcmgr.LimitVal(s.NumStreamsOutbound),
288
- Conns: rcmgr.LimitVal(s.NumConnsInbound + s.NumConnsOutbound),
289
- ConnsInbound: rcmgr.LimitVal(s.NumConnsInbound),
290
- ConnsOutbound: rcmgr.LimitVal(s.NumConnsOutbound),
291
- FD: rcmgr.LimitVal(s.NumFD),
292
- Memory: rcmgr.LimitVal64(s.Memory),
162
+ for _, v := range cfg.Peer {
163
+ if v != emptyResourceConfig {
164
+ return false
165
+ }
166
}
167
+ return true
168
}
169
296
-// compareLimits compares stat and limit.
297
-// If any of the stats value are equals or above the specified percentage,
298
-// it returns true.
299
-func compareLimits(stat, limit notOmitEmptyResourceLimit, percentage int) bool {
300
- if abovePercentage(int(stat.Memory), int(limit.Memory), percentage) {
301
- return true
302
- }
303
- if abovePercentage(stat.ConnsInbound, limit.ConnsInbound, percentage) {
304
- return true
305
- }
306
- if abovePercentage(stat.ConnsOutbound, limit.ConnsOutbound, percentage) {
307
- return true
308
- }
309
- if abovePercentage(stat.Conns, limit.Conns, percentage) {
310
- return true
311
- }
312
- if abovePercentage(stat.FD, limit.FD, percentage) {
313
- return true
314
- }
315
- if abovePercentage(stat.StreamsInbound, limit.StreamsInbound, percentage) {
316
- return true
317
- }
318
- if abovePercentage(stat.StreamsOutbound, limit.StreamsOutbound, percentage) {
319
- return true
320
- }
321
- if abovePercentage(stat.Streams, limit.Streams, percentage) {
322
- return true
170
+// LimitConfig returns the union of the Computed Default Limits and the User Supplied Override Limits.
171
+func LimitConfig(cfg config.SwarmConfig, userResourceOverrides rcmgr.PartialLimitConfig) (limitConfig rcmgr.ConcreteLimitConfig, logMessageForStartup string, err error) {
172
+ limitConfig, msg, err := createDefaultLimitConfig(cfg)
173
+ if err != nil {
174
+ return rcmgr.ConcreteLimitConfig{}, msg, err
175
}
176
325
- return false
326
-}
327
-
328
-func abovePercentage[T constraints.Integer | constraints.Float](v1, v2 T, percentage int) bool {
329
- if percentage == 0 {
330
- return true
331
- }
177
+ // The logic for defaults and overriding with specified userResourceOverrides
178
+ // is documented in docs/libp2p-resource-management.md.
179
+ // Any changes here should be reflected there.
180
333
- if v2 == 0 {
334
- return false
335
- }
181
+ // This effectively overrides the computed default LimitConfig with any non-"useDefault" values from the userResourceOverrides file.
182
+ // Because of how how Build works, any rcmgr.Default value in userResourceOverrides
183
+ // will be overriden with a computed default value.
184
+ limitConfig = userResourceOverrides.Build(limitConfig)
185
337
- return int((float64(v1)/float64(v2))*100) >= percentage
186
+ return limitConfig, msg, nil
187
}
188
340
-func NetLimitAll(mgr network.ResourceManager) (*NetStatOut, error) {
341
- var result = &NetStatOut{}
342
- lister, ok := mgr.(rcmgr.ResourceManagerState)
343
- if !ok { // NullResourceManager
344
- return result, ErrNoResourceMgr
345
- }
189
+type ResourceLimitsAndUsage struct {
190
+ // This is duplicated from rcmgr.ResourceResourceLimits but adding *Usage fields.
191
+ Memory rcmgr.LimitVal64
192
+ MemoryUsage int64
193
+ FD rcmgr.LimitVal
194
+ FDUsage int
195
+ Conns rcmgr.LimitVal
196
+ ConnsUsage int
197
+ ConnsInbound rcmgr.LimitVal
198
+ ConnsInboundUsage int
199
+ ConnsOutbound rcmgr.LimitVal
200
+ ConnsOutboundUsage int
201
+ Streams rcmgr.LimitVal
202
+ StreamsUsage int
203
+ StreamsInbound rcmgr.LimitVal
204
+ StreamsInboundUsage int
205
+ StreamsOutbound rcmgr.LimitVal
206
+ StreamsOutboundUsage int
207
+}
208
347
- for _, s := range scopes {
348
- switch s {
349
- case config.ResourceMgrSystemScope:
350
- s, err := NetLimit(mgr, config.ResourceMgrSystemScope)
351
- if err != nil {
352
- return nil, err
353
- }
354
- result.System = &s
355
- case config.ResourceMgrTransientScope:
356
- s, err := NetLimit(mgr, config.ResourceMgrSystemScope)
357
- if err != nil {
358
- return nil, err
359
- }
360
- result.Transient = &s
361
- case config.ResourceMgrServiceScopePrefix:
362
- result.Services = make(map[string]notOmitEmptyResourceLimit)
363
- for _, serv := range lister.ListServices() {
364
- s, err := NetLimit(mgr, config.ResourceMgrServiceScopePrefix+serv)
365
- if err != nil {
366
- return nil, err
367
- }
368
- result.Services[serv] = s
369
- }
370
- case config.ResourceMgrProtocolScopePrefix:
371
- result.Protocols = make(map[string]notOmitEmptyResourceLimit)
372
- for _, prot := range lister.ListProtocols() {
373
- ps := string(prot)
374
- s, err := NetLimit(mgr, config.ResourceMgrProtocolScopePrefix+ps)
375
- if err != nil {
376
- return nil, err
377
- }
378
- result.Protocols[ps] = s
379
- }
380
- case config.ResourceMgrPeerScopePrefix:
381
- result.Peers = make(map[string]notOmitEmptyResourceLimit)
382
- for _, peer := range lister.ListPeers() {
383
- ps := peer.Pretty()
384
- s, err := NetLimit(mgr, config.ResourceMgrPeerScopePrefix+ps)
385
- if err != nil {
386
- return nil, err
387
- }
388
- result.Peers[ps] = s
389
- }
390
- }
209
+func (u ResourceLimitsAndUsage) ToResourceLimits() rcmgr.ResourceLimits {
210
+ return rcmgr.ResourceLimits{
211
+ Memory: u.Memory,
212
+ FD: u.FD,
213
+ Conns: u.Conns,
214
+ ConnsInbound: u.ConnsInbound,
215
+ ConnsOutbound: u.ConnsOutbound,
216
+ Streams: u.Streams,
217
+ StreamsInbound: u.StreamsInbound,
218
+ StreamsOutbound: u.StreamsOutbound,
219
}
220
+}
221
393
- return result, nil
222
+type LimitsConfigAndUsage struct {
223
+ // This is duplicated from rcmgr.ResourceManagerStat but using ResourceLimitsAndUsage
224
+ // instead of network.ScopeStat.
225
+ System ResourceLimitsAndUsage `json:",omitempty"`
226
+ Transient ResourceLimitsAndUsage `json:",omitempty"`
227
+ Services map[string]ResourceLimitsAndUsage `json:",omitempty"`
228
+ Protocols map[protocol.ID]ResourceLimitsAndUsage `json:",omitempty"`
229
+ Peers map[peer.ID]ResourceLimitsAndUsage `json:",omitempty"`
230
}
231
396
-func NetLimit(mgr network.ResourceManager, scope string) (notOmitEmptyResourceLimit, error) {
397
- var result rcmgr.ResourceLimits
398
- getLimit := func(s network.ResourceScope) error {
399
- limiter, ok := s.(rcmgr.ResourceScopeLimiter)
400
- if !ok { // NullResourceManager
401
- return ErrNoResourceMgr
402
- }
232
+func (u LimitsConfigAndUsage) MarshalJSON() ([]byte, error) {
233
+ // we want to marshal the encoded peer id
234
+ encodedPeerMap := make(map[string]ResourceLimitsAndUsage, len(u.Peers))
235
+ for p, v := range u.Peers {
236
+ encodedPeerMap[p.String()] = v
237
+ }
238
+
239
+ type Alias LimitsConfigAndUsage
240
+ return json.Marshal(&struct {
241
+ *Alias
242
+ Peers map[string]ResourceLimitsAndUsage `json:",omitempty"`
243
+ }{
244
+ Alias: (*Alias)(&u),
245
+ Peers: encodedPeerMap,
246
+ })
247
+}
248
404
- switch limit := limiter.Limit(); l := limit.(type) {
405
- case *rcmgr.BaseLimit:
406
- result = l.ToResourceLimits()
407
- case rcmgr.BaseLimit:
408
- result = l.ToResourceLimits()
409
- default:
410
- return fmt.Errorf("unknown limit type %T", limit)
411
- }
249
+func (u LimitsConfigAndUsage) ToPartialLimitConfig() (result rcmgr.PartialLimitConfig) {
250
+ result.System = u.System.ToResourceLimits()
251
+ result.Transient = u.Transient.ToResourceLimits()
252
413
- return nil
253
+ result.Service = make(map[string]rcmgr.ResourceLimits, len(u.Services))
254
+ for s, l := range u.Services {
255
+ result.Service[s] = l.ToResourceLimits()
256
}
415
-
416
- var err error
417
- switch {
418
- case scope == config.ResourceMgrSystemScope:
419
- err = mgr.ViewSystem(func(s network.ResourceScope) error { return getLimit(s) })
420
- case scope == config.ResourceMgrTransientScope:
421
- err = mgr.ViewTransient(func(s network.ResourceScope) error { return getLimit(s) })
422
- case strings.HasPrefix(scope, config.ResourceMgrServiceScopePrefix):
423
- svc := strings.TrimPrefix(scope, config.ResourceMgrServiceScopePrefix)
424
- err = mgr.ViewService(svc, func(s network.ServiceScope) error { return getLimit(s) })
425
- case strings.HasPrefix(scope, config.ResourceMgrProtocolScopePrefix):
426
- proto := strings.TrimPrefix(scope, config.ResourceMgrProtocolScopePrefix)
427
- err = mgr.ViewProtocol(protocol.ID(proto), func(s network.ProtocolScope) error { return getLimit(s) })
428
- case strings.HasPrefix(scope, config.ResourceMgrPeerScopePrefix):
429
- p := strings.TrimPrefix(scope, config.ResourceMgrPeerScopePrefix)
430
- var pid peer.ID
431
- pid, err = peer.Decode(p)
432
- if err != nil {
433
- return notOmitEmptyResourceLimit{}, fmt.Errorf("invalid peer ID: %q: %w", p, err)
434
- }
435
- err = mgr.ViewPeer(pid, func(s network.PeerScope) error { return getLimit(s) })
436
- default:
437
- err = fmt.Errorf("invalid scope %q", scope)
257
+ result.Protocol = make(map[protocol.ID]rcmgr.ResourceLimits, len(u.Protocols))
258
+ for p, l := range u.Protocols {
259
+ result.Protocol[p] = l.ToResourceLimits()
260
+ }
261
+ result.Peer = make(map[peer.ID]rcmgr.ResourceLimits, len(u.Peers))
262
+ for p, l := range u.Peers {
263
+ result.Peer[p] = l.ToResourceLimits()
264
}
439
- return resourceLimitsToNotOmitEmpty(result), err
440
-}
265
442
-// NetSetLimit sets new ResourceManager limits for the given scope. The limits take effect immediately, and are also persisted to the repo config.
443
-func NetSetLimit(mgr network.ResourceManager, repo repo.Repo, scope string, limit rcmgr.ResourceLimits) error {
444
- setLimit := func(s network.ResourceScope) error {
445
- limiter, ok := s.(rcmgr.ResourceScopeLimiter)
446
- if !ok { // NullResourceManager
447
- return ErrNoResourceMgr
448
- }
266
+ return
267
+}
268
450
- l := rcmgr.InfiniteLimits.ToPartialLimitConfig().System
451
- limiter.SetLimit(limit.Build(l.Build(rcmgr.BaseLimit{})))
452
- return nil
453
- }
269
+func MergeLimitsAndStatsIntoLimitsConfigAndUsage(l rcmgr.ConcreteLimitConfig, stats rcmgr.ResourceManagerStat) LimitsConfigAndUsage {
270
+ limits := l.ToPartialLimitConfig()
271
455
- cfg, err := repo.Config()
456
- if err != nil {
457
- return fmt.Errorf("reading config to set limit: %w", err)
272
+ return LimitsConfigAndUsage{
273
+ System: mergeResourceLimitsAndScopeStatToResourceLimitsAndUsage(limits.System, stats.System),
274
+ Transient: mergeResourceLimitsAndScopeStatToResourceLimitsAndUsage(limits.Transient, stats.Transient),
275
+ Services: mergeLimitsAndStatsMapIntoLimitsConfigAndUsageMap(limits.Service, stats.Services),
276
+ Protocols: mergeLimitsAndStatsMapIntoLimitsConfigAndUsageMap(limits.Protocol, stats.Protocols),
277
+ Peers: mergeLimitsAndStatsMapIntoLimitsConfigAndUsageMap(limits.Peer, stats.Peers),
278
}
279
+}
280
460
- if cfg.Swarm.ResourceMgr.Limits == nil {
461
- cfg.Swarm.ResourceMgr.Limits = &rcmgr.PartialLimitConfig{}
462
- }
463
- configLimits := cfg.Swarm.ResourceMgr.Limits
464
-
465
- var setConfigFunc func()
466
- switch {
467
- case scope == config.ResourceMgrSystemScope:
468
- err = mgr.ViewSystem(func(s network.ResourceScope) error { return setLimit(s) })
469
- setConfigFunc = func() { configLimits.System = limit }
470
- case scope == config.ResourceMgrTransientScope:
471
- err = mgr.ViewTransient(func(s network.ResourceScope) error { return setLimit(s) })
472
- setConfigFunc = func() { configLimits.Transient = limit }
473
- case strings.HasPrefix(scope, config.ResourceMgrServiceScopePrefix):
474
- svc := strings.TrimPrefix(scope, config.ResourceMgrServiceScopePrefix)
475
- err = mgr.ViewService(svc, func(s network.ServiceScope) error { return setLimit(s) })
476
- setConfigFunc = func() {
477
- if configLimits.Service == nil {
478
- configLimits.Service = map[string]rcmgr.ResourceLimits{}
479
- }
480
- configLimits.Service[svc] = limit
481
- }
482
- case strings.HasPrefix(scope, config.ResourceMgrProtocolScopePrefix):
483
- proto := strings.TrimPrefix(scope, config.ResourceMgrProtocolScopePrefix)
484
- err = mgr.ViewProtocol(protocol.ID(proto), func(s network.ProtocolScope) error { return setLimit(s) })
485
- setConfigFunc = func() {
486
- if configLimits.Protocol == nil {
487
- configLimits.Protocol = map[protocol.ID]rcmgr.ResourceLimits{}
488
- }
489
- configLimits.Protocol[protocol.ID(proto)] = limit
490
- }
491
- case strings.HasPrefix(scope, config.ResourceMgrPeerScopePrefix):
492
- p := strings.TrimPrefix(scope, config.ResourceMgrPeerScopePrefix)
493
- var pid peer.ID
494
- pid, err = peer.Decode(p)
495
- if err != nil {
496
- return fmt.Errorf("invalid peer ID: %q: %w", p, err)
497
- }
498
- err = mgr.ViewPeer(pid, func(s network.PeerScope) error { return setLimit(s) })
499
- setConfigFunc = func() {
500
- if configLimits.Peer == nil {
501
- configLimits.Peer = map[peer.ID]rcmgr.ResourceLimits{}
281
+func mergeLimitsAndStatsMapIntoLimitsConfigAndUsageMap[K comparable](limits map[K]rcmgr.ResourceLimits, stats map[K]network.ScopeStat) map[K]ResourceLimitsAndUsage {
282
+ r := make(map[K]ResourceLimitsAndUsage, maxInt(len(limits), len(stats)))
283
+ for p, s := range stats {
284
+ var l rcmgr.ResourceLimits
285
+ if limits != nil {
286
+ if rl, ok := limits[p]; ok {
287
+ l = rl
288
}
503
- configLimits.Peer[pid] = limit
289
}
505
- default:
506
- return fmt.Errorf("invalid scope %q", scope)
290
+ r[p] = mergeResourceLimitsAndScopeStatToResourceLimitsAndUsage(l, s)
291
}
292
+ for p, s := range limits {
293
+ if _, ok := stats[p]; ok {
294
+ continue // we already processed this element in the loop above
295
+ }
296
509
- if err != nil {
510
- return fmt.Errorf("setting new limits on resource manager: %w", err)
297
+ r[p] = mergeResourceLimitsAndScopeStatToResourceLimitsAndUsage(s, network.ScopeStat{})
298
}
299
+ return r
300
+}
301
513
- if cfg.Swarm.ResourceMgr.Limits == nil {
514
- cfg.Swarm.ResourceMgr.Limits = &rcmgr.PartialLimitConfig{}
302
+func maxInt(x, y int) int {
303
+ if x > y {
304
+ return x
305
}
516
- setConfigFunc()
306
+ return y
307
+}
308
518
- if err := repo.SetConfig(cfg); err != nil {
519
- return fmt.Errorf("writing new limits to repo config: %w", err)
309
+func mergeResourceLimitsAndScopeStatToResourceLimitsAndUsage(rl rcmgr.ResourceLimits, ss network.ScopeStat) ResourceLimitsAndUsage {
310
+ return ResourceLimitsAndUsage{
311
+ Memory: rl.Memory,
312
+ MemoryUsage: ss.Memory,
313
+ FD: rl.FD,
314
+ FDUsage: ss.NumFD,
315
+ Conns: rl.Conns,
316
+ ConnsUsage: ss.NumConnsOutbound + ss.NumConnsInbound,
317
+ ConnsOutbound: rl.ConnsOutbound,
318
+ ConnsOutboundUsage: ss.NumConnsOutbound,
319
+ ConnsInbound: rl.ConnsInbound,
320
+ ConnsInboundUsage: ss.NumConnsInbound,
321
+ Streams: rl.Streams,
322
+ StreamsUsage: ss.NumStreamsOutbound + ss.NumConnsInbound,
323
+ StreamsOutbound: rl.StreamsOutbound,
324
+ StreamsOutboundUsage: ss.NumConnsOutbound,
325
+ StreamsInbound: rl.StreamsInbound,
326
+ StreamsInboundUsage: ss.NumConnsInbound,
327
}
328
+}
329
522
- return nil
330
+type ResourceInfos []ResourceInfo
331
+
332
+type ResourceInfo struct {
333
+ ScopeName string
334
+ LimitName string
335
+ LimitValue rcmgr.LimitVal64
336
+ CurrentUsage int64
337
}
338
525
-// NetResetLimit resets ResourceManager limits to defaults. The limits take effect immediately, and are also persisted to the repo config.
526
-func NetResetLimit(mgr network.ResourceManager, repo repo.Repo, scope string) (rcmgr.BaseLimit, error) {
527
- var result rcmgr.BaseLimit
339
+// LimitConfigsToInfo gets limits and stats and generates a list of scopes and limits to be printed.
340
+func LimitConfigsToInfo(stats LimitsConfigAndUsage) ResourceInfos {
341
+ result := ResourceInfos{}
342
529
- setLimit := func(s network.ResourceScope, l rcmgr.Limit) error {
530
- limiter, ok := s.(rcmgr.ResourceScopeLimiter)
531
- if !ok {
532
- return ErrNoResourceMgr
533
- }
343
+ result = append(result, resourceLimitsAndUsageToResourceInfo(config.ResourceMgrSystemScope, stats.System)...)
344
+ result = append(result, resourceLimitsAndUsageToResourceInfo(config.ResourceMgrTransientScope, stats.Transient)...)
345
535
- limiter.SetLimit(l)
536
- return nil
346
+ for i, s := range stats.Services {
347
+ result = append(result, resourceLimitsAndUsageToResourceInfo(
348
+ config.ResourceMgrServiceScopePrefix+i,
349
+ s,
350
+ )...)
351
}
352
539
- cfg, err := repo.Config()
540
- if err != nil {
541
- return rcmgr.BaseLimit{}, fmt.Errorf("reading config to reset limit: %w", err)
353
+ for i, p := range stats.Protocols {
354
+ result = append(result, resourceLimitsAndUsageToResourceInfo(
355
+ config.ResourceMgrProtocolScopePrefix+string(i),
356
+ p,
357
+ )...)
358
}
359
544
- defaultsOrig, err := createDefaultLimitConfig(cfg.Swarm)
545
- if err != nil {
546
- return rcmgr.BaseLimit{}, fmt.Errorf("creating default limit config: %w", err)
360
+ for i, p := range stats.Peers {
361
+ result = append(result, resourceLimitsAndUsageToResourceInfo(
362
+ config.ResourceMgrPeerScopePrefix+i.Pretty(),
363
+ p,
364
+ )...)
365
}
548
- defaults := defaultsOrig.ToPartialLimitConfig()
366
550
- // INVESTIGATE(@Jorropo): Why do we save scaled configs in the repo ?
367
+ return result
368
+}
369
552
- if cfg.Swarm.ResourceMgr.Limits == nil {
553
- cfg.Swarm.ResourceMgr.Limits = &rcmgr.PartialLimitConfig{}
554
- }
555
- configLimits := cfg.Swarm.ResourceMgr.Limits
556
-
557
- var setConfigFunc func() rcmgr.BaseLimit
558
- switch {
559
- case scope == config.ResourceMgrSystemScope:
560
- err = mgr.ViewSystem(func(s network.ResourceScope) error { return setLimit(s, defaults.System.Build(rcmgr.BaseLimit{})) })
561
- setConfigFunc = func() rcmgr.BaseLimit {
562
- configLimits.System = defaults.System
563
- return defaults.System.Build(rcmgr.BaseLimit{})
564
- }
565
- case scope == config.ResourceMgrTransientScope:
566
- err = mgr.ViewTransient(func(s network.ResourceScope) error { return setLimit(s, defaults.Transient.Build(rcmgr.BaseLimit{})) })
567
- setConfigFunc = func() rcmgr.BaseLimit {
568
- configLimits.Transient = defaults.Transient
569
- return defaults.Transient.Build(rcmgr.BaseLimit{})
570
- }
571
- case strings.HasPrefix(scope, config.ResourceMgrServiceScopePrefix):
572
- svc := strings.TrimPrefix(scope, config.ResourceMgrServiceScopePrefix)
573
-
574
- err = mgr.ViewService(svc, func(s network.ServiceScope) error {
575
- return setLimit(s, defaults.ServiceDefault.Build(rcmgr.BaseLimit{}))
576
- })
577
- setConfigFunc = func() rcmgr.BaseLimit {
578
- if configLimits.Service == nil {
579
- configLimits.Service = map[string]rcmgr.ResourceLimits{}
580
- }
581
- configLimits.Service[svc] = defaults.ServiceDefault
582
- return defaults.ServiceDefault.Build(rcmgr.BaseLimit{})
583
- }
584
- case strings.HasPrefix(scope, config.ResourceMgrProtocolScopePrefix):
585
- proto := strings.TrimPrefix(scope, config.ResourceMgrProtocolScopePrefix)
586
-
587
- err = mgr.ViewProtocol(protocol.ID(proto), func(s network.ProtocolScope) error {
588
- return setLimit(s, defaults.ProtocolDefault.Build(rcmgr.BaseLimit{}))
589
- })
590
- setConfigFunc = func() rcmgr.BaseLimit {
591
- if configLimits.Protocol == nil {
592
- configLimits.Protocol = map[protocol.ID]rcmgr.ResourceLimits{}
593
- }
594
- configLimits.Protocol[protocol.ID(proto)] = defaults.ProtocolDefault
370
+const (
371
+ limitNameMemory = "Memory"
372
+ limitNameFD = "FD"
373
+ limitNameConns = "Conns"
374
+ limitNameConnsInbound = "ConnsInbound"
375
+ limitNameConnsOutbound = "ConnsOutbound"
376
+ limitNameStreams = "Streams"
377
+ limitNameStreamsInbound = "StreamsInbound"
378
+ limitNameStreamsOutbound = "StreamsOutbound"
379
+)
380
596
- return defaults.ProtocolDefault.Build(rcmgr.BaseLimit{})
597
- }
598
- case strings.HasPrefix(scope, config.ResourceMgrPeerScopePrefix):
599
- p := strings.TrimPrefix(scope, config.ResourceMgrPeerScopePrefix)
381
+var limits = []string{
382
+ limitNameMemory,
383
+ limitNameFD,
384
+ limitNameConns,
385
+ limitNameConnsInbound,
386
+ limitNameConnsOutbound,
387
+ limitNameStreams,
388
+ limitNameStreamsInbound,
389
+ limitNameStreamsOutbound,
390
+}
391
601
- var pid peer.ID
602
- pid, err = peer.Decode(p)
603
- if err != nil {
604
- return result, fmt.Errorf("invalid peer ID: %q: %w", p, err)
392
+func resourceLimitsAndUsageToResourceInfo(scopeName string, stats ResourceLimitsAndUsage) ResourceInfos {
393
+ result := ResourceInfos{}
394
+ for _, l := range limits {
395
+ ri := ResourceInfo{
396
+ ScopeName: scopeName,
397
}
606
-
607
- err = mgr.ViewPeer(pid, func(s network.PeerScope) error { return setLimit(s, defaults.PeerDefault.Build(rcmgr.BaseLimit{})) })
608
- setConfigFunc = func() rcmgr.BaseLimit {
609
- if configLimits.Peer == nil {
610
- configLimits.Peer = map[peer.ID]rcmgr.ResourceLimits{}
611
- }
612
- configLimits.Peer[pid] = defaults.PeerDefault
613
-
614
- return defaults.PeerDefault.Build(rcmgr.BaseLimit{})
398
+ switch l {
399
+ case limitNameMemory:
400
+ ri.LimitName = limitNameMemory
401
+ ri.LimitValue = stats.Memory
402
+ ri.CurrentUsage = stats.MemoryUsage
403
+ case limitNameFD:
404
+ ri.LimitName = limitNameFD
405
+ ri.LimitValue = rcmgr.LimitVal64(stats.FD)
406
+ ri.CurrentUsage = int64(stats.FDUsage)
407
+ case limitNameConns:
408
+ ri.LimitName = limitNameConns
409
+ ri.LimitValue = rcmgr.LimitVal64(stats.Conns)
410
+ ri.CurrentUsage = int64(stats.ConnsUsage)
411
+ case limitNameConnsInbound:
412
+ ri.LimitName = limitNameConnsInbound
413
+ ri.LimitValue = rcmgr.LimitVal64(stats.ConnsInbound)
414
+ ri.CurrentUsage = int64(stats.ConnsInboundUsage)
415
+ case limitNameConnsOutbound:
416
+ ri.LimitName = limitNameConnsOutbound
417
+ ri.LimitValue = rcmgr.LimitVal64(stats.ConnsOutbound)
418
+ ri.CurrentUsage = int64(stats.ConnsOutboundUsage)
419
+ case limitNameStreams:
420
+ ri.LimitName = limitNameStreams
421
+ ri.LimitValue = rcmgr.LimitVal64(stats.Streams)
422
+ ri.CurrentUsage = int64(stats.StreamsUsage)
423
+ case limitNameStreamsInbound:
424
+ ri.LimitName = limitNameStreamsInbound
425
+ ri.LimitValue = rcmgr.LimitVal64(stats.StreamsInbound)
426
+ ri.CurrentUsage = int64(stats.StreamsInboundUsage)
427
+ case limitNameStreamsOutbound:
428
+ ri.LimitName = limitNameStreamsOutbound
429
+ ri.LimitValue = rcmgr.LimitVal64(stats.StreamsOutbound)
430
+ ri.CurrentUsage = int64(stats.StreamsOutboundUsage)
431
}
616
- default:
617
- return result, fmt.Errorf("invalid scope %q", scope)
618
- }
619
-
620
- if err != nil {
621
- return result, fmt.Errorf("resetting new limits on resource manager: %w", err)
622
- }
432
624
- result = setConfigFunc()
433
+ if ri.LimitValue == rcmgr.Unlimited64 || ri.LimitValue == rcmgr.DefaultLimit64 {
434
+ // ignore unlimited and unset limits to remove noise from output.
435
+ continue
436
+ }
437
626
- if err := repo.SetConfig(cfg); err != nil {
627
- return result, fmt.Errorf("writing new limits to repo config: %w", err)
438
+ result = append(result, ri)
439
}
440
630
- return result, nil
441
+ return result
442
}
443
633
-func ensureConnMgrMakeSenseVsResourceMgr(orig rcmgr.ConcreteLimitConfig, cmgr config.ConnMgr) error {
634
- if cmgr.Type.WithDefault(config.DefaultConnMgrType) == "none" {
635
- return nil // none connmgr, no checks to do
444
+func ensureConnMgrMakeSenseVsResourceMgr(concreteLimits rcmgr.ConcreteLimitConfig, cfg config.SwarmConfig) error {
445
+ if cfg.ConnMgr.Type.WithDefault(config.DefaultConnMgrType) == "none" || len(cfg.ResourceMgr.Allowlist) != 0 {
446
+ // no connmgr OR
447
+ // If an allowlist is set, a user may be enacting some form of DoS defense.
448
+ // We don't want want to modify the System.ConnsInbound in that case for example
449
+ // as it may make sense for it to be (and stay) as "blockAll"
450
+ // so that only connections within the allowlist of multiaddrs get established.
451
+ return nil
452
}
453
638
- rcm := orig.ToPartialLimitConfig()
454
+ rcm := concreteLimits.ToPartialLimitConfig()
455
640
- highWater := cmgr.HighWater.WithDefault(config.DefaultConnMgrHighWater)
641
- if rcm.System.ConnsInbound <= rcm.System.Conns {
642
- if int64(rcm.System.ConnsInbound) <= highWater {
643
- // nolint
644
- return fmt.Errorf(`
645
-Unable to initialize libp2p due to conflicting limit configuration:
646
-ResourceMgr.Limits.System.ConnsInbound (%d) must be bigger than ConnMgr.HighWater (%d)
647
-`, rcm.System.ConnsInbound, highWater)
648
- }
649
- } else if int64(rcm.System.Conns) <= highWater {
456
+ highWater := cfg.ConnMgr.HighWater.WithDefault(config.DefaultConnMgrHighWater)
457
+ if rcm.System.Conns != rcmgr.Unlimited && int64(rcm.System.Conns) <= highWater {
458
// nolint
459
return fmt.Errorf(`
652
-Unable to initialize libp2p due to conflicting limit configuration:
653
-ResourceMgr.Limits.System.Conns (%d) must be bigger than ConnMgr.HighWater (%d)
460
+Unable to initialize libp2p due to conflicting resource manager limit configuration.
461
+resource manager System.Conns (%d) must be bigger than ConnMgr.HighWater (%d)
462
`, rcm.System.Conns, highWater)
463
}
656
- if rcm.System.StreamsInbound <= rcm.System.Streams {
657
- if int64(rcm.System.StreamsInbound) <= highWater {
658
- // nolint
659
- return fmt.Errorf(`
660
-Unable to initialize libp2p due to conflicting limit configuration:
661
-ResourceMgr.Limits.System.StreamsInbound (%d) must be bigger than ConnMgr.HighWater (%d)
662
-`, rcm.System.StreamsInbound, highWater)
663
- }
664
- } else if int64(rcm.System.Streams) <= highWater {
464
+ if rcm.System.ConnsInbound != rcmgr.Unlimited && int64(rcm.System.ConnsInbound) <= highWater {
465
// nolint
466
return fmt.Errorf(`
667
-Unable to initialize libp2p due to conflicting limit configuration:
668
-ResourceMgr.Limits.System.Streams (%d) must be bigger than ConnMgr.HighWater (%d)
467
+Unable to initialize libp2p due to conflicting resource manager limit configuration.
468
+resource manager System.ConnsInbound (%d) must be bigger than ConnMgr.HighWater (%d)
469
+`, rcm.System.ConnsInbound, highWater)
470
+ }
471
+ if rcm.System.Streams != rcmgr.Unlimited && int64(rcm.System.Streams) <= highWater {
472
+ // nolint
473
+ return fmt.Errorf(`
474
+Unable to initialize libp2p due to conflicting resource manager limit configuration.
475
+resource manager System.Streams (%d) must be bigger than ConnMgr.HighWater (%d)
476
`, rcm.System.Streams, highWater)
477
+ }
478
+ if rcm.System.StreamsInbound != rcmgr.Unlimited && int64(rcm.System.StreamsInbound) <= highWater {
479
+ // nolint
480
+ return fmt.Errorf(`
481
+Unable to initialize libp2p due to conflicting resource manager limit configuration.
482
+resource manager System.StreamsInbound (%d) must be bigger than ConnMgr.HighWater (%d)
483
+`, rcm.System.StreamsInbound, highWater)
484
}
485
return nil
486
}
core/node/libp2p/rcmgr_defaults.go
+61
-113
@@ -4,70 +4,31 @@ import (
4
"fmt"
5
6
"github.com/dustin/go-humanize"
7
+ "github.com/ipfs/kubo/config"
8
+ "github.com/ipfs/kubo/core/node/libp2p/fd"
9
"github.com/libp2p/go-libp2p"
10
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
11
"github.com/pbnjay/memory"
10
-
11
- "github.com/ipfs/kubo/config"
12
- "github.com/ipfs/kubo/core/node/libp2p/fd"
12
)
13
15
-// We are doing some magic when parsing config files (we are using a map[string]interface{} to compare config files).
16
-// When you don't have a type the JSON Parse function cast numbers to float64 by default,
17
-// losing precision when writing the final number. So if we use math.MaxInt as our infinite number,
18
-// after writing the config file we will have 9223372036854776000 instead of 9223372036854775807,
19
-// making the parsing process fail. Setting 1e9 (1000000000) as "no limit" value. It also avoids to overflow on 32 bit architectures.
20
-const bigEnough = 1e9
21
-
22
-var infiniteBaseLimit = rcmgr.BaseLimit{
23
- Streams: bigEnough,
24
- StreamsInbound: bigEnough,
25
- StreamsOutbound: bigEnough,
26
- Conns: bigEnough,
27
- ConnsInbound: bigEnough,
28
- ConnsOutbound: bigEnough,
29
- FD: bigEnough,
30
- Memory: bigEnough,
31
-}
32
-
33
-var noLimitIncrease = rcmgr.BaseLimitIncrease{
34
- ConnsInbound: 0,
35
- ConnsOutbound: 0,
36
- Conns: 0,
37
- StreamsInbound: 0,
38
- StreamsOutbound: 0,
39
- Streams: 0,
40
- Memory: 0,
41
- FDFraction: 0,
42
-}
14
+var infiniteResourceLimits = rcmgr.InfiniteLimits.ToPartialLimitConfig().System
15
16
// This file defines implicit limit defaults used when Swarm.ResourceMgr.Enabled
17
18
// createDefaultLimitConfig creates LimitConfig to pass to libp2p's resource manager.
19
// The defaults follow the documentation in docs/libp2p-resource-management.md.
20
// Any changes in the logic here should be reflected there.
49
-func createDefaultLimitConfig(cfg config.SwarmConfig) (rcmgr.ConcreteLimitConfig, error) {
21
+func createDefaultLimitConfig(cfg config.SwarmConfig) (limitConfig rcmgr.ConcreteLimitConfig, logMessageForStartup string, err error) {
22
maxMemoryDefaultString := humanize.Bytes(uint64(memory.TotalMemory()) / 2)
23
maxMemoryString := cfg.ResourceMgr.MaxMemory.WithDefault(maxMemoryDefaultString)
24
maxMemory, err := humanize.ParseBytes(maxMemoryString)
25
if err != nil {
54
- return rcmgr.ConcreteLimitConfig{}, err
26
+ return rcmgr.ConcreteLimitConfig{}, "", err
27
}
28
29
maxMemoryMB := maxMemory / (1024 * 1024)
30
maxFD := int(cfg.ResourceMgr.MaxFileDescriptors.WithDefault(int64(fd.GetNumFDs()) / 2))
31
60
- // We want to see this message on startup, that's why we are using fmt instead of log.
61
- fmt.Printf(`
62
-Computing default go-libp2p Resource Manager limits based on:
63
- - 'Swarm.ResourceMgr.MaxMemory': %q
64
- - 'Swarm.ResourceMgr.MaxFileDescriptors': %d
65
-
66
-Applying any user-supplied overrides on top.
67
-Run 'ipfs swarm limit all' to see the resulting limits.
68
-
69
-`, maxMemoryString, maxFD)
70
-
32
// At least as of 2023-01-25, it's possible to open a connection that
33
// doesn't ask for any memory usage with the libp2p Resource Manager/Accountant
34
// (see https://github.com/libp2p/go-libp2p/issues/2010#issuecomment-1404280736).
@@ -79,109 +40,86 @@ Run 'ipfs swarm limit all' to see the resulting limits.
40
// (see https://github.com/libp2p/go-libp2p/blob/master/p2p/host/resource-manager/limit_defaults.go#L357 ).
41
systemConnsInbound := int(1 * maxMemoryMB)
42
82
- scalingLimitConfig := rcmgr.ScalingLimitConfig{
83
- SystemBaseLimit: rcmgr.BaseLimit{
84
- Memory: int64(maxMemory),
85
- FD: maxFD,
43
+ partialLimits := rcmgr.PartialLimitConfig{
44
+ System: rcmgr.ResourceLimits{
45
+ Memory: rcmgr.LimitVal64(maxMemory),
46
+ FD: rcmgr.LimitVal(maxFD),
47
87
- // By default, we just limit connections on the inbound side.
88
- Conns: bigEnough,
89
- ConnsInbound: systemConnsInbound,
90
- ConnsOutbound: bigEnough,
48
+ Conns: rcmgr.Unlimited,
49
+ ConnsInbound: rcmgr.LimitVal(systemConnsInbound),
50
+ ConnsOutbound: rcmgr.Unlimited,
51
92
- Streams: bigEnough,
93
- StreamsInbound: bigEnough,
94
- StreamsOutbound: bigEnough,
52
+ Streams: rcmgr.Unlimited,
53
+ StreamsOutbound: rcmgr.Unlimited,
54
+ StreamsInbound: rcmgr.Unlimited,
55
},
96
- SystemLimitIncrease: noLimitIncrease,
56
98
- // Transient connections won't cause any memory to accounted for by the resource manager.
57
+ // Transient connections won't cause any memory to be accounted for by the resource manager/accountant.
58
// Only established connections do.
59
// As a result, we can't rely on System.Memory to protect us from a bunch of transient connection being opened.
60
// We limit the same values as the System scope, but only allow the Transient scope to take 25% of what is allowed for the System scope.
102
- TransientBaseLimit: rcmgr.BaseLimit{
103
- Memory: int64(maxMemory / 4),
104
- FD: maxFD / 4,
61
+ Transient: rcmgr.ResourceLimits{
62
+ Memory: rcmgr.LimitVal64(maxMemory / 4),
63
+ FD: rcmgr.LimitVal(maxFD / 4),
64
106
- Conns: bigEnough,
107
- ConnsInbound: systemConnsInbound / 4,
108
- ConnsOutbound: bigEnough,
65
+ Conns: rcmgr.Unlimited,
66
+ ConnsInbound: rcmgr.LimitVal(systemConnsInbound / 4),
67
+ ConnsOutbound: rcmgr.Unlimited,
68
110
- Streams: bigEnough,
111
- StreamsInbound: bigEnough,
112
- StreamsOutbound: bigEnough,
69
+ Streams: rcmgr.Unlimited,
70
+ StreamsInbound: rcmgr.Unlimited,
71
+ StreamsOutbound: rcmgr.Unlimited,
72
},
73
115
- TransientLimitIncrease: noLimitIncrease,
116
-
74
// Lets get out of the way of the allow list functionality.
75
// If someone specified "Swarm.ResourceMgr.Allowlist" we should let it go through.
119
- AllowlistedSystemBaseLimit: infiniteBaseLimit,
120
- AllowlistedSystemLimitIncrease: noLimitIncrease,
76
+ AllowlistedSystem: infiniteResourceLimits,
77
122
- AllowlistedTransientBaseLimit: infiniteBaseLimit,
123
- AllowlistedTransientLimitIncrease: noLimitIncrease,
78
+ AllowlistedTransient: infiniteResourceLimits,
79
80
// Keep it simple by not having Service, ServicePeer, Protocol, ProtocolPeer, Conn, or Stream limits.
126
- ServiceBaseLimit: infiniteBaseLimit,
127
- ServiceLimitIncrease: noLimitIncrease,
81
+ ServiceDefault: infiniteResourceLimits,
82
129
- ServicePeerBaseLimit: infiniteBaseLimit,
130
- ServicePeerLimitIncrease: noLimitIncrease,
83
+ ServicePeerDefault: infiniteResourceLimits,
84
132
- ProtocolBaseLimit: infiniteBaseLimit,
133
- ProtocolLimitIncrease: noLimitIncrease,
85
+ ProtocolDefault: infiniteResourceLimits,
86
135
- ProtocolPeerBaseLimit: infiniteBaseLimit,
136
- ProtocolPeerLimitIncrease: noLimitIncrease,
87
+ ProtocolPeerDefault: infiniteResourceLimits,
88
138
- ConnBaseLimit: infiniteBaseLimit,
139
- ConnLimitIncrease: noLimitIncrease,
89
+ Conn: infiniteResourceLimits,
90
141
- StreamBaseLimit: infiniteBaseLimit,
142
- StreamLimitIncrease: noLimitIncrease,
91
+ Stream: infiniteResourceLimits,
92
93
// Limit the resources consumed by a peer.
94
// This doesn't protect us against intentional DoS attacks since an attacker can easily spin up multiple peers.
95
// We specify this limit against unintentional DoS attacks (e.g., a peer has a bug and is sending too much traffic intentionally).
96
// In that case we want to keep that peer's resource consumption contained.
97
// To keep this simple, we only constrain inbound connections and streams.
149
- PeerBaseLimit: rcmgr.BaseLimit{
150
- Memory: bigEnough,
151
- FD: bigEnough,
152
- Conns: bigEnough,
153
- ConnsInbound: rcmgr.DefaultLimits.PeerBaseLimit.ConnsInbound,
154
- ConnsOutbound: bigEnough,
155
- Streams: bigEnough,
156
- StreamsInbound: rcmgr.DefaultLimits.PeerBaseLimit.StreamsInbound,
157
- StreamsOutbound: bigEnough,
158
- },
159
- // Most limits don't see an increase because they're already infinite/bigEnough.
160
- // The values that should scale based on the amount of memory allocated to libp2p need to increase accordingly.
161
- PeerLimitIncrease: rcmgr.BaseLimitIncrease{
162
- Memory: 0,
163
- FDFraction: 0,
164
- Conns: 0,
165
- ConnsInbound: rcmgr.DefaultLimits.PeerLimitIncrease.ConnsInbound,
166
- ConnsOutbound: 0,
167
- Streams: 0,
168
- StreamsInbound: rcmgr.DefaultLimits.PeerLimitIncrease.StreamsInbound,
169
- StreamsOutbound: 0,
98
+ PeerDefault: rcmgr.ResourceLimits{
99
+ Memory: rcmgr.Unlimited64,
100
+ FD: rcmgr.Unlimited,
101
+ Conns: rcmgr.Unlimited,
102
+ ConnsInbound: rcmgr.DefaultLimit,
103
+ ConnsOutbound: rcmgr.Unlimited,
104
+ Streams: rcmgr.Unlimited,
105
+ StreamsInbound: rcmgr.DefaultLimit,
106
+ StreamsOutbound: rcmgr.Unlimited,
107
},
108
}
109
173
- // Whatever limits libp2p has specifically tuned for its protocols/services we'll apply.
110
+ scalingLimitConfig := rcmgr.DefaultLimits
111
libp2p.SetDefaultServiceLimits(&scalingLimitConfig)
112
176
- orig := scalingLimitConfig.Scale(int64(maxMemory), maxFD)
177
- defaultLimitConfig := orig.ToPartialLimitConfig()
113
+ // Anything set above in partialLimits that had a value of rcmgr.DefaultLimit will be overridden.
114
+ // Anything in scalingLimitConfig that wasn't defined in partialLimits above will be added (e.g., libp2p's default service limits).
115
+ partialLimits = partialLimits.Build(scalingLimitConfig.Scale(int64(maxMemory), maxFD)).ToPartialLimitConfig()
116
117
// Simple checks to overide autoscaling ensuring limits make sense versus the connmgr values.
118
// There are ways to break this, but this should catch most problems already.
119
// We might improve this in the future.
120
// See: https://github.com/ipfs/kubo/issues/9545
183
- if cfg.ConnMgr.Type.WithDefault(config.DefaultConnMgrType) != "none" {
184
- maxInboundConns := int64(defaultLimitConfig.System.ConnsInbound)
121
+ if partialLimits.System.ConnsInbound != rcmgr.Unlimited && cfg.ConnMgr.Type.WithDefault(config.DefaultConnMgrType) != "none" {
122
+ maxInboundConns := int64(partialLimits.System.ConnsInbound)
123
if connmgrHighWaterTimesTwo := cfg.ConnMgr.HighWater.WithDefault(config.DefaultConnMgrHighWater) * 2; maxInboundConns < connmgrHighWaterTimesTwo {
124
maxInboundConns = connmgrHighWaterTimesTwo
125
}
@@ -191,9 +129,19 @@ Run 'ipfs swarm limit all' to see the resulting limits.
129
}
130
131
// Scale System.StreamsInbound as well, but use the existing ratio of StreamsInbound to ConnsInbound
194
- defaultLimitConfig.System.StreamsInbound = rcmgr.LimitVal(maxInboundConns * int64(defaultLimitConfig.System.StreamsInbound) / int64(defaultLimitConfig.System.ConnsInbound))
195
- defaultLimitConfig.System.ConnsInbound = rcmgr.LimitVal(maxInboundConns)
132
+ partialLimits.System.StreamsInbound = rcmgr.LimitVal(maxInboundConns * int64(partialLimits.System.StreamsInbound) / int64(partialLimits.System.ConnsInbound))
133
+ partialLimits.System.ConnsInbound = rcmgr.LimitVal(maxInboundConns)
134
}
135
198
- return defaultLimitConfig.Build(orig), nil
136
+ msg := fmt.Sprintf(`
137
+Computed default go-libp2p Resource Manager limits based on:
138
+ - 'Swarm.ResourceMgr.MaxMemory': %q
139
+ - 'Swarm.ResourceMgr.MaxFileDescriptors': %d
140
+
141
+Theses can be inspected with 'ipfs swarm resources'.
142
+
143
+`, maxMemoryString, maxFD)
144
+
145
+ // We already have a complete value thus pass in an empty ConcreteLimitConfig.
146
+ return partialLimits.Build(rcmgr.ConcreteLimitConfig{}), msg, nil
147
}
core/node/libp2p/rcmgr_test.go
deleted
-12
@@ -1,12 +0,0 @@
1
-package libp2p
2
-
3
-import (
4
- "testing"
5
-
6
- "github.com/stretchr/testify/require"
7
-)
8
-
9
-func TestPercentage(t *testing.T) {
10
- require.True(t, abovePercentage(10, 100, 10))
11
- require.True(t, abovePercentage(100, 100, 99))
12
-}
core/node/storage.go
+2
-1
@@ -14,7 +14,8 @@ import (
14
15
// RepoConfig loads configuration from the repo
16
func RepoConfig(repo repo.Repo) (*config.Config, error) {
17
- return repo.Config()
17
+ cfg, err := repo.Config()
18
+ return cfg, err
19
}
20
21
// Datastore provides the datastore
go.mod
+1
-1
@@ -106,7 +106,6 @@ require (
106
go.uber.org/fx v1.18.2
107
go.uber.org/zap v1.24.0
108
golang.org/x/crypto v0.5.0
109
- golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2
109
golang.org/x/mod v0.7.0
110
golang.org/x/sync v0.1.0
111
golang.org/x/sys v0.5.0
@@ -228,6 +227,7 @@ require (
227
go.uber.org/atomic v1.10.0 // indirect
228
go.uber.org/multierr v1.9.0 // indirect
229
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
230
+ golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect
231
golang.org/x/net v0.7.0 // indirect
232
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
233
golang.org/x/term v0.5.0 // indirect
repo/fsrepo/fsrepo.go
+37
-5
@@ -16,6 +16,7 @@ import (
16
repo "github.com/ipfs/kubo/repo"
17
"github.com/ipfs/kubo/repo/common"
18
dir "github.com/ipfs/kubo/thirdparty/dir"
19
+ rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
20
21
ds "github.com/ipfs/go-datastore"
22
measure "github.com/ipfs/go-ds-measure"
@@ -102,11 +103,12 @@ type FSRepo struct {
103
configFilePath string
104
// lockfile is the file system lock to prevent others from opening
105
// the same fsrepo path concurrently
105
- lockfile io.Closer
106
- config *config.Config
107
- ds repo.Datastore
108
- keystore keystore.Keystore
109
- filemgr *filestore.FileManager
106
+ lockfile io.Closer
107
+ config *config.Config
108
+ userResourceOverrides rcmgr.PartialLimitConfig
109
+ ds repo.Datastore
110
+ keystore keystore.Keystore
111
+ filemgr *filestore.FileManager
112
}
113
114
var _ repo.Repo = (*FSRepo)(nil)
@@ -180,6 +182,10 @@ func open(repoPath string, userConfigFilePath string) (repo.Repo, error) {
182
return nil, err
183
}
184
185
+ if err := r.openUserResourceOverrides(); err != nil {
186
+ return nil, err
187
+ }
188
+
189
if err := r.openDatastore(); err != nil {
190
return nil, err
191
}
@@ -437,6 +443,17 @@ func (r *FSRepo) openConfig() error {
443
return nil
444
}
445
446
+// openUserResourceOverrides will remove all overrides if the file is not present.
447
+// It will error if the decoding fails.
448
+func (r *FSRepo) openUserResourceOverrides() error {
449
+ // This filepath is documented in docs/libp2p-resource-management.md and be kept in sync.
450
+ err := serialize.ReadConfigFile(filepath.Join(r.path, "libp2p-resource-limit-overrides.json"), &r.userResourceOverrides)
451
+ if err == serialize.ErrNotInitialized {
452
+ err = nil
453
+ }
454
+ return err
455
+}
456
+
457
func (r *FSRepo) openKeystore() error {
458
ksp := filepath.Join(r.path, "keystore")
459
ks, err := keystore.NewFSKeystore(ksp)
@@ -554,6 +571,21 @@ func (r *FSRepo) Config() (*config.Config, error) {
571
return r.config, nil
572
}
573
574
+func (r *FSRepo) UserResourceOverrides() (rcmgr.PartialLimitConfig, error) {
575
+ // It is not necessary to hold the package lock since the repo is in an
576
+ // opened state. The package lock is _not_ meant to ensure that the repo is
577
+ // thread-safe. The package lock is only meant to guard against removal and
578
+ // coordinate the lockfile. However, we provide thread-safety to keep
579
+ // things simple.
580
+ packageLock.Lock()
581
+ defer packageLock.Unlock()
582
+
583
+ if r.closed {
584
+ return rcmgr.PartialLimitConfig{}, errors.New("cannot access config, repo not open")
585
+ }
586
+ return r.userResourceOverrides, nil
587
+}
588
+
589
func (r *FSRepo) FileManager() *filestore.FileManager {
590
return r.filemgr
591
}
repo/mock.go
+5
@@ -7,6 +7,7 @@ import (
7
8
filestore "github.com/ipfs/go-filestore"
9
keystore "github.com/ipfs/go-ipfs-keystore"
10
+ rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
11
12
config "github.com/ipfs/kubo/config"
13
ma "github.com/multiformats/go-multiaddr"
@@ -26,6 +27,10 @@ func (m *Mock) Config() (*config.Config, error) {
27
return &m.C, nil // FIXME threadsafety
28
}
29
30
+func (m *Mock) UserResourceOverrides() (rcmgr.PartialLimitConfig, error) {
31
+ return rcmgr.PartialLimitConfig{}, nil
32
+}
33
+
34
func (m *Mock) SetConfig(updated *config.Config) error {
35
m.C = *updated // FIXME threadsafety
36
return nil
repo/repo.go
+5
@@ -8,6 +8,7 @@ import (
8
9
filestore "github.com/ipfs/go-filestore"
10
keystore "github.com/ipfs/go-ipfs-keystore"
11
+ rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
12
13
ds "github.com/ipfs/go-datastore"
14
config "github.com/ipfs/kubo/config"
@@ -24,6 +25,10 @@ type Repo interface {
25
// to the returned config are not automatically persisted.
26
Config() (*config.Config, error)
27
28
+ // UserResourceOverrides returns optional user resource overrides for the
29
+ // libp2p resource manager.
30
+ UserResourceOverrides() (rcmgr.PartialLimitConfig, error)
31
+
32
// BackupConfig creates a backup of the current configuration file using
33
// the given prefix for naming.
34
BackupConfig(prefix string) (string, error)
test/cli/basic_commands_test.go
+1
@@ -88,6 +88,7 @@ func TestAllSubcommandsAcceptHelp(t *testing.T) {
88
t.Parallel()
89
node := harness.NewT(t).NewNode()
90
for _, cmd := range node.IPFSCommands() {
91
+ cmd := cmd
92
t.Run(fmt.Sprintf("command %q accepts help", cmd), func(t *testing.T) {
93
t.Parallel()
94
splitCmd := strings.Split(cmd, " ")[1:]
test/cli/harness/node.go
+33
@@ -20,6 +20,7 @@ import (
20
"github.com/ipfs/kubo/config"
21
serial "github.com/ipfs/kubo/config/serialize"
22
"github.com/libp2p/go-libp2p/core/peer"
23
+ rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
24
"github.com/multiformats/go-multiaddr"
25
manet "github.com/multiformats/go-multiaddr/net"
26
)
@@ -96,6 +97,38 @@ func (n *Node) UpdateConfig(f func(cfg *config.Config)) {
97
n.WriteConfig(cfg)
98
}
99
100
+func (n *Node) ReadUserResourceOverrides() *rcmgr.PartialLimitConfig {
101
+ var r rcmgr.PartialLimitConfig
102
+ err := serial.ReadConfigFile(filepath.Join(n.Dir, "libp2p-resource-limit-overrides.json"), &r)
103
+ switch err {
104
+ case nil, serial.ErrNotInitialized:
105
+ return &r
106
+ default:
107
+ panic(err)
108
+ }
109
+}
110
+
111
+func (n *Node) WriteUserSuppliedResourceOverrides(c *rcmgr.PartialLimitConfig) {
112
+ err := serial.WriteConfigFile(filepath.Join(n.Dir, "libp2p-resource-limit-overrides.json"), c)
113
+ if err != nil {
114
+ panic(err)
115
+ }
116
+}
117
+
118
+func (n *Node) UpdateUserSuppliedResourceManagerOverrides(f func(overrides *rcmgr.PartialLimitConfig)) {
119
+ overrides := n.ReadUserResourceOverrides()
120
+ f(overrides)
121
+ n.WriteUserSuppliedResourceOverrides(overrides)
122
+}
123
+
124
+func (n *Node) UpdateConfigAndUserSuppliedResourceManagerOverrides(f func(cfg *config.Config, overrides *rcmgr.PartialLimitConfig)) {
125
+ overrides := n.ReadUserResourceOverrides()
126
+ cfg := n.ReadConfig()
127
+ f(cfg, overrides)
128
+ n.WriteConfig(cfg)
129
+ n.WriteUserSuppliedResourceOverrides(overrides)
130
+}
131
+
132
func (n *Node) IPFS(args ...string) RunResult {
133
res := n.RunIPFS(args...)
134
n.Runner.AssertNoError(res)
test/cli/rcmgr_test.go
+93
-183
@@ -7,6 +7,8 @@ import (
7
"github.com/ipfs/kubo/config"
8
"github.com/ipfs/kubo/core/node/libp2p"
9
"github.com/ipfs/kubo/test/cli/harness"
10
+ "github.com/libp2p/go-libp2p/core/peer"
11
+ "github.com/libp2p/go-libp2p/core/protocol"
12
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
13
"github.com/stretchr/testify/assert"
14
"github.com/stretchr/testify/require"
@@ -24,19 +26,14 @@ func TestRcmgr(t *testing.T) {
26
27
node.StartDaemon()
28
27
- t.Run("swarm limit should fail", func(t *testing.T) {
28
- res := node.RunIPFS("swarm", "limit", "system")
29
+ t.Run("swarm resources should fail", func(t *testing.T) {
30
+ res := node.RunIPFS("swarm", "resources")
31
assert.Equal(t, 1, res.ExitCode())
30
- assert.Contains(t, res.Stderr.Lines()[0], "missing ResourceMgr")
31
- })
32
- t.Run("swarm stats should fail", func(t *testing.T) {
33
- res := node.RunIPFS("swarm", "stats", "all")
34
- assert.Equal(t, 1, res.ExitCode())
35
- assert.Contains(t, res.Stderr.Lines()[0], "missing ResourceMgr")
32
+ assert.Contains(t, res.Stderr.String(), "missing ResourceMgr")
33
})
34
})
35
39
- t.Run("Node in offline mode", func(t *testing.T) {
36
+ t.Run("Node with resource manager disabled", func(t *testing.T) {
37
t.Parallel()
38
node := harness.NewT(t).NewNode().Init()
39
node.UpdateConfig(func(cfg *config.Config) {
@@ -44,15 +41,10 @@ func TestRcmgr(t *testing.T) {
41
})
42
node.StartDaemon()
43
47
- t.Run("swarm limit should fail", func(t *testing.T) {
48
- res := node.RunIPFS("swarm", "limit", "system")
49
- assert.Equal(t, 1, res.ExitCode())
50
- assert.Contains(t, res.Stderr.Lines()[0], "missing ResourceMgr")
51
- })
52
- t.Run("swarm stats should fail", func(t *testing.T) {
53
- res := node.RunIPFS("swarm", "stats", "all")
44
+ t.Run("swarm resources should fail", func(t *testing.T) {
45
+ res := node.RunIPFS("swarm", "resources")
46
assert.Equal(t, 1, res.ExitCode())
55
- assert.Contains(t, res.Stderr.Lines()[0], "missing ResourceMgr")
47
+ assert.Contains(t, res.Stderr.String(), "missing ResourceMgr")
48
})
49
})
50
@@ -63,12 +55,14 @@ func TestRcmgr(t *testing.T) {
55
})
56
node.StartDaemon()
57
66
- res := node.RunIPFS("swarm", "limit", "system", "--enc=json")
58
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
59
require.Equal(t, 0, res.ExitCode())
60
limits := unmarshalLimits(t, res.Stdout.Bytes())
61
70
- assert.GreaterOrEqual(t, limits.ConnsInbound, 2000)
71
- assert.GreaterOrEqual(t, limits.StreamsInbound, 2000)
62
+ rl := limits.System.ToResourceLimits()
63
+ s := rl.Build(rcmgr.BaseLimit{})
64
+ assert.GreaterOrEqual(t, s.ConnsInbound, 2000)
65
+ assert.GreaterOrEqual(t, s.StreamsInbound, 2000)
66
})
67
68
t.Run("default configuration", func(t *testing.T) {
@@ -80,176 +74,102 @@ func TestRcmgr(t *testing.T) {
74
node.StartDaemon()
75
76
t.Run("conns and streams are above 800 for default connmgr settings", func(t *testing.T) {
83
- res := node.RunIPFS("swarm", "limit", "system", "--enc=json")
77
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
78
require.Equal(t, 0, res.ExitCode())
79
limits := unmarshalLimits(t, res.Stdout.Bytes())
80
87
- assert.GreaterOrEqual(t, limits.ConnsInbound, 800)
88
- assert.GreaterOrEqual(t, limits.StreamsInbound, 800)
81
+ if limits.System.ConnsInbound != rcmgr.Unlimited {
82
+ assert.GreaterOrEqual(t, limits.System.ConnsInbound, 800)
83
+ }
84
+ if limits.System.StreamsInbound != rcmgr.Unlimited {
85
+ assert.GreaterOrEqual(t, limits.System.StreamsInbound, 800)
86
+ }
87
})
88
91
- t.Run("limits|stats should succeed", func(t *testing.T) {
92
- res := node.RunIPFS("swarm", "limit", "all")
89
+ t.Run("limits should succeed", func(t *testing.T) {
90
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
91
assert.Equal(t, 0, res.ExitCode())
92
95
- limits := map[string]rcmgr.ResourceLimits{}
93
+ limits := rcmgr.PartialLimitConfig{}
94
err := json.Unmarshal(res.Stdout.Bytes(), &limits)
95
require.NoError(t, err)
96
99
- assert.Greater(t, limits["System"].Memory, int64(0))
100
- assert.Greater(t, limits["System"].FD, 0)
101
- assert.Greater(t, limits["System"].Conns, 0)
102
- assert.Greater(t, limits["System"].ConnsInbound, 0)
103
- assert.Greater(t, limits["System"].ConnsOutbound, 0)
104
- assert.Greater(t, limits["System"].Streams, 0)
105
- assert.Greater(t, limits["System"].StreamsInbound, 0)
106
- assert.Greater(t, limits["System"].StreamsOutbound, 0)
107
- assert.Greater(t, limits["Transient"].Memory, int64(0))
108
- })
109
-
110
- t.Run("resetting limits should produce the same default limits", func(t *testing.T) {
111
- resetRes := node.RunIPFS("swarm", "limit", "system", "--reset", "--enc=json")
112
- require.Equal(t, 0, resetRes.ExitCode())
113
- limitRes := node.RunIPFS("swarm", "limit", "system", "--enc=json")
114
- require.Equal(t, 0, limitRes.ExitCode())
115
-
116
- assert.Equal(t, resetRes.Stdout.Bytes(), limitRes.Stdout.Bytes())
117
- })
118
-
119
- t.Run("swarm stats system with filter should fail", func(t *testing.T) {
120
- res := node.RunIPFS("swarm", "stats", "system", "--min-used-limit-perc=99")
121
- assert.Equal(t, 1, res.ExitCode())
122
- assert.Contains(t, res.Stderr.Lines()[0], `Error: "min-used-limit-perc" can only be used when scope is "all"`)
123
- })
124
-
125
- t.Run("swarm limit reset on map values should work", func(t *testing.T) {
126
- resetRes := node.RunIPFS("swarm", "limit", "peer:12D3KooWL7i1T9VSPeF8AgQApbyM51GNKZsYPvNvL347aMDmvNzG", "--reset", "--enc=json")
127
- require.Equal(t, 0, resetRes.ExitCode())
128
- limitRes := node.RunIPFS("swarm", "limit", "peer:12D3KooWL7i1T9VSPeF8AgQApbyM51GNKZsYPvNvL347aMDmvNzG", "--enc=json")
129
- require.Equal(t, 0, limitRes.ExitCode())
130
-
131
- assert.Equal(t, resetRes.Stdout.Bytes(), limitRes.Stdout.Bytes())
132
- })
133
-
134
- t.Run("scope is required using reset flags", func(t *testing.T) {
135
- res := node.RunIPFS("swarm", "limit", "--reset")
136
- assert.Equal(t, 1, res.ExitCode())
137
- assert.Contains(t, res.Stderr.Lines()[0], `Error: argument "scope" is required`)
97
+ assert.NotEqual(t, limits.Transient.Memory, rcmgr.BlockAllLimit64)
98
+ assert.NotEqual(t, limits.System.Memory, rcmgr.BlockAllLimit64)
99
+ assert.NotEqual(t, limits.System.FD, rcmgr.BlockAllLimit)
100
+ assert.NotEqual(t, limits.System.Conns, rcmgr.BlockAllLimit)
101
+ assert.NotEqual(t, limits.System.ConnsInbound, rcmgr.BlockAllLimit)
102
+ assert.NotEqual(t, limits.System.ConnsOutbound, rcmgr.BlockAllLimit)
103
+ assert.NotEqual(t, limits.System.Streams, rcmgr.BlockAllLimit)
104
+ assert.NotEqual(t, limits.System.StreamsInbound, rcmgr.BlockAllLimit)
105
+ assert.NotEqual(t, limits.System.StreamsOutbound, rcmgr.BlockAllLimit)
106
})
107
108
t.Run("swarm stats works", func(t *testing.T) {
141
- res := node.RunIPFS("swarm", "stats", "all", "--enc=json")
109
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
110
require.Equal(t, 0, res.ExitCode())
111
144
- stats := libp2p.NetStatOut{}
145
- err := json.Unmarshal(res.Stdout.Bytes(), &stats)
146
- require.NoError(t, err)
112
+ limits := unmarshalLimits(t, res.Stdout.Bytes())
113
114
// every scope has the same fields, so we only inspect system
149
- assert.Equal(t, rcmgr.LimitVal64(0), stats.System.Memory)
150
- assert.Equal(t, rcmgr.LimitVal(0), stats.System.FD)
151
- assert.Equal(t, rcmgr.LimitVal(0), stats.System.Conns)
152
- assert.Equal(t, rcmgr.LimitVal(0), stats.System.ConnsInbound)
153
- assert.Equal(t, rcmgr.LimitVal(0), stats.System.ConnsOutbound)
154
- assert.Equal(t, rcmgr.LimitVal(0), stats.System.Streams)
155
- assert.Equal(t, rcmgr.LimitVal(0), stats.System.StreamsInbound)
156
- assert.Equal(t, rcmgr.LimitVal(0), stats.System.StreamsOutbound)
157
- assert.Equal(t, rcmgr.LimitVal64(0), stats.Transient.Memory)
115
+ assert.Zero(t, limits.System.MemoryUsage)
116
+ assert.Zero(t, limits.System.FDUsage)
117
+ assert.Zero(t, limits.System.ConnsInboundUsage)
118
+ assert.Zero(t, limits.System.ConnsOutboundUsage)
119
+ assert.Zero(t, limits.System.StreamsInboundUsage)
120
+ assert.Zero(t, limits.System.StreamsOutboundUsage)
121
+ assert.Zero(t, limits.Transient.MemoryUsage)
122
})
123
})
124
161
- t.Run("set system conns limit while daemon is not running", func(t *testing.T) {
125
+ t.Run("smoke test transient scope", func(t *testing.T) {
126
node := harness.NewT(t).NewNode().Init()
163
- res := node.RunIPFS("config", "--json", "Swarm.ResourceMgr.Limits.System.Conns", "99999")
164
- require.Equal(t, 0, res.ExitCode())
165
-
166
- t.Run("set an invalid limit which should result in a failure", func(t *testing.T) {
167
- res := node.RunIPFS("config", "--json", "Swarm.ResourceMgr.Limits.System.Conns", "asdf")
168
- assert.Equal(t, 1, res.ExitCode())
169
- assert.Contains(t, res.Stderr.String(), "failed to unmarshal")
127
+ node.UpdateUserSuppliedResourceManagerOverrides(func(overrides *rcmgr.PartialLimitConfig) {
128
+ overrides.Transient.Memory = 88888
129
})
171
-
130
node.StartDaemon()
131
174
- t.Run("new system conns limit is applied", func(t *testing.T) {
175
- res := node.RunIPFS("swarm", "limit", "system", "--enc=json")
176
- limits := unmarshalLimits(t, res.Stdout.Bytes())
177
- assert.Equal(t, limits.Conns, rcmgr.LimitVal(99999))
178
- })
179
- })
180
-
181
- t.Run("set the system memory limit while the daemon is running", func(t *testing.T) {
182
- node := harness.NewT(t).NewNode().Init().StartDaemon()
183
- updateLimitsWithFile(t, node, "system", func(limits *rcmgr.ResourceLimits) {
184
- limits.Memory = 99998
185
- })
186
-
187
- assert.Equal(t, rcmgr.LimitVal64(99998), node.ReadConfig().Swarm.ResourceMgr.Limits.System.Memory)
188
-
189
- res := node.RunIPFS("swarm", "limit", "system", "--enc=json")
190
- limits := unmarshalLimits(t, res.Stdout.Bytes())
191
- assert.Equal(t, rcmgr.LimitVal64(99998), limits.Memory)
192
- })
193
-
194
- t.Run("smoke test transient scope", func(t *testing.T) {
195
- node := harness.NewT(t).NewNode().Init().StartDaemon()
196
- updateLimitsWithFile(t, node, "transient", func(limits *rcmgr.ResourceLimits) {
197
- limits.Memory = 88888
198
- })
199
-
200
- res := node.RunIPFS("swarm", "limit", "transient", "--enc=json")
132
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
133
limits := unmarshalLimits(t, res.Stdout.Bytes())
202
- assert.Equal(t, rcmgr.LimitVal64(88888), limits.Memory)
134
+ assert.Equal(t, rcmgr.LimitVal64(88888), limits.Transient.Memory)
135
})
136
137
t.Run("smoke test service scope", func(t *testing.T) {
206
- node := harness.NewT(t).NewNode().Init().StartDaemon()
207
- updateLimitsWithFile(t, node, "svc:foo", func(limits *rcmgr.ResourceLimits) {
208
- limits.Memory = 77777
138
+ node := harness.NewT(t).NewNode().Init()
139
+ node.UpdateUserSuppliedResourceManagerOverrides(func(overrides *rcmgr.PartialLimitConfig) {
140
+ overrides.Service = map[string]rcmgr.ResourceLimits{"foo": {Memory: 77777}}
141
})
142
+ node.StartDaemon()
143
211
- res := node.RunIPFS("swarm", "limit", "svc:foo", "--enc=json")
144
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
145
limits := unmarshalLimits(t, res.Stdout.Bytes())
213
- assert.Equal(t, rcmgr.LimitVal64(77777), limits.Memory)
146
+ assert.Equal(t, rcmgr.LimitVal64(77777), limits.Services["foo"].Memory)
147
})
148
149
t.Run("smoke test protocol scope", func(t *testing.T) {
217
- node := harness.NewT(t).NewNode().Init().StartDaemon()
218
- updateLimitsWithFile(t, node, "proto:foo", func(limits *rcmgr.ResourceLimits) {
219
- limits.Memory = 66666
150
+ node := harness.NewT(t).NewNode().Init()
151
+ node.UpdateUserSuppliedResourceManagerOverrides(func(overrides *rcmgr.PartialLimitConfig) {
152
+ overrides.Protocol = map[protocol.ID]rcmgr.ResourceLimits{"foo": {Memory: 66666}}
153
})
154
+ node.StartDaemon()
155
222
- res := node.RunIPFS("swarm", "limit", "proto:foo", "--enc=json")
156
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
157
limits := unmarshalLimits(t, res.Stdout.Bytes())
224
- assert.Equal(t, rcmgr.LimitVal64(66666), limits.Memory)
158
+ assert.Equal(t, rcmgr.LimitVal64(66666), limits.Protocols["foo"].Memory)
159
})
160
161
t.Run("smoke test peer scope", func(t *testing.T) {
228
- validPeerID := "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"
229
- node := harness.NewT(t).NewNode().Init().StartDaemon()
230
- updateLimitsWithFile(t, node, "peer:"+validPeerID, func(limits *rcmgr.ResourceLimits) {
231
- limits.Memory = 66666
162
+ validPeerID, err := peer.Decode("QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN")
163
+ assert.NoError(t, err)
164
+ node := harness.NewT(t).NewNode().Init()
165
+ node.UpdateUserSuppliedResourceManagerOverrides(func(overrides *rcmgr.PartialLimitConfig) {
166
+ overrides.Peer = map[peer.ID]rcmgr.ResourceLimits{validPeerID: {Memory: 55555}}
167
})
168
+ node.StartDaemon()
169
234
- res := node.RunIPFS("swarm", "limit", "peer:"+validPeerID, "--enc=json")
170
+ res := node.RunIPFS("swarm", "resources", "--enc=json")
171
limits := unmarshalLimits(t, res.Stdout.Bytes())
236
- assert.Equal(t, rcmgr.LimitVal64(66666), limits.Memory)
237
-
238
- t.Parallel()
239
-
240
- t.Run("getting limit for invalid peer ID fails", func(t *testing.T) {
241
- res := node.RunIPFS("swarm", "limit", "peer:foo")
242
- assert.Equal(t, 1, res.ExitCode())
243
- assert.Contains(t, res.Stderr.String(), "invalid peer ID")
244
- })
245
-
246
- t.Run("setting limit for invalid peer ID fails", func(t *testing.T) {
247
- filename := "invalid-peer-id.json"
248
- node.WriteBytes(filename, []byte(`{"Memory":"99"}`))
249
- res := node.RunIPFS("swarm", "limit", "peer:foo", filename)
250
- assert.Equal(t, 1, res.ExitCode())
251
- assert.Contains(t, res.Stderr.String(), "invalid peer ID")
252
- })
172
+ assert.Equal(t, rcmgr.LimitVal64(55555), limits.Peers[validPeerID].Memory)
173
})
174
175
t.Run("", func(t *testing.T) {
@@ -258,20 +178,20 @@ func TestRcmgr(t *testing.T) {
178
// peerID0, peerID1, peerID2 := node0.PeerID(), node1.PeerID(), node2.PeerID()
179
peerID1, peerID2 := node1.PeerID().String(), node2.PeerID().String()
180
261
- node0.UpdateConfig(func(cfg *config.Config) {
181
+ node0.UpdateConfigAndUserSuppliedResourceManagerOverrides(func(cfg *config.Config, overrides *rcmgr.PartialLimitConfig) {
182
+ *overrides = rcmgr.PartialLimitConfig{
183
+ System: rcmgr.ResourceLimits{
184
+ Conns: rcmgr.BlockAllLimit,
185
+ ConnsInbound: rcmgr.BlockAllLimit,
186
+ ConnsOutbound: rcmgr.BlockAllLimit,
187
+ },
188
+ }
189
cfg.Swarm.ResourceMgr.Enabled = config.True
190
cfg.Swarm.ResourceMgr.Allowlist = []string{"/ip4/0.0.0.0/ipcidr/0/p2p/" + peerID2}
191
})
192
193
nodes.StartDaemons()
194
268
- // change system limits on node 0
269
- updateLimitsWithFile(t, node0, "system", func(limits *rcmgr.ResourceLimits) {
270
- limits.Conns = rcmgr.BlockAllLimit
271
- limits.ConnsInbound = rcmgr.BlockAllLimit
272
- limits.ConnsOutbound = rcmgr.BlockAllLimit
273
- })
274
-
195
t.Parallel()
196
t.Run("node 0 should fail to connect to node 1", func(t *testing.T) {
197
res := node0.Runner.Run(harness.RunRequest{
@@ -306,9 +226,10 @@ func TestRcmgr(t *testing.T) {
226
t.Parallel()
227
t.Run("system conns", func(t *testing.T) {
228
node := harness.NewT(t).NewNode().Init()
309
- node.UpdateConfig(func(cfg *config.Config) {
310
- cfg.Swarm.ResourceMgr.Limits = &rcmgr.PartialLimitConfig{}
311
- cfg.Swarm.ResourceMgr.Limits.System.Conns = 128
229
+ node.UpdateConfigAndUserSuppliedResourceManagerOverrides(func(cfg *config.Config, overrides *rcmgr.PartialLimitConfig) {
230
+ *overrides = rcmgr.PartialLimitConfig{
231
+ System: rcmgr.ResourceLimits{Conns: 128},
232
+ }
233
cfg.Swarm.ConnMgr.HighWater = config.NewOptionalInteger(128)
234
cfg.Swarm.ConnMgr.LowWater = config.NewOptionalInteger(64)
235
})
@@ -318,9 +239,10 @@ func TestRcmgr(t *testing.T) {
239
})
240
t.Run("system conns inbound", func(t *testing.T) {
241
node := harness.NewT(t).NewNode().Init()
321
- node.UpdateConfig(func(cfg *config.Config) {
322
- cfg.Swarm.ResourceMgr.Limits = &rcmgr.PartialLimitConfig{}
323
- cfg.Swarm.ResourceMgr.Limits.System.ConnsInbound = 128
242
+ node.UpdateConfigAndUserSuppliedResourceManagerOverrides(func(cfg *config.Config, overrides *rcmgr.PartialLimitConfig) {
243
+ *overrides = rcmgr.PartialLimitConfig{
244
+ System: rcmgr.ResourceLimits{ConnsInbound: 128},
245
+ }
246
cfg.Swarm.ConnMgr.HighWater = config.NewOptionalInteger(128)
247
cfg.Swarm.ConnMgr.LowWater = config.NewOptionalInteger(64)
248
})
@@ -330,9 +252,10 @@ func TestRcmgr(t *testing.T) {
252
})
253
t.Run("system streams", func(t *testing.T) {
254
node := harness.NewT(t).NewNode().Init()
333
- node.UpdateConfig(func(cfg *config.Config) {
334
- cfg.Swarm.ResourceMgr.Limits = &rcmgr.PartialLimitConfig{}
335
- cfg.Swarm.ResourceMgr.Limits.System.Streams = 128
255
+ node.UpdateConfigAndUserSuppliedResourceManagerOverrides(func(cfg *config.Config, overrides *rcmgr.PartialLimitConfig) {
256
+ *overrides = rcmgr.PartialLimitConfig{
257
+ System: rcmgr.ResourceLimits{Streams: 128},
258
+ }
259
cfg.Swarm.ConnMgr.HighWater = config.NewOptionalInteger(128)
260
cfg.Swarm.ConnMgr.LowWater = config.NewOptionalInteger(64)
261
})
@@ -342,9 +265,10 @@ func TestRcmgr(t *testing.T) {
265
})
266
t.Run("system streams inbound", func(t *testing.T) {
267
node := harness.NewT(t).NewNode().Init()
345
- node.UpdateConfig(func(cfg *config.Config) {
346
- cfg.Swarm.ResourceMgr.Limits = &rcmgr.PartialLimitConfig{}
347
- cfg.Swarm.ResourceMgr.Limits.System.StreamsInbound = 128
268
+ node.UpdateConfigAndUserSuppliedResourceManagerOverrides(func(cfg *config.Config, overrides *rcmgr.PartialLimitConfig) {
269
+ *overrides = rcmgr.PartialLimitConfig{
270
+ System: rcmgr.ResourceLimits{StreamsInbound: 128},
271
+ }
272
cfg.Swarm.ConnMgr.HighWater = config.NewOptionalInteger(128)
273
cfg.Swarm.ConnMgr.LowWater = config.NewOptionalInteger(64)
274
})
@@ -355,22 +279,8 @@ func TestRcmgr(t *testing.T) {
279
})
280
}
281
358
-func updateLimitsWithFile(t *testing.T, node *harness.Node, limit string, f func(*rcmgr.ResourceLimits)) {
359
- filename := limit + ".json"
360
- res := node.RunIPFS("swarm", "limit", limit)
361
- limits := unmarshalLimits(t, res.Stdout.Bytes())
362
-
363
- f(limits)
364
-
365
- limitsOut, err := json.Marshal(limits)
366
- require.NoError(t, err)
367
- node.WriteBytes(filename, limitsOut)
368
- res = node.RunIPFS("swarm", "limit", limit, filename)
369
- assert.Equal(t, 0, res.ExitCode())
370
-}
371
-
372
-func unmarshalLimits(t *testing.T, b []byte) *rcmgr.ResourceLimits {
373
- limits := &rcmgr.ResourceLimits{}
282
+func unmarshalLimits(t *testing.T, b []byte) *libp2p.LimitsConfigAndUsage {
283
+ limits := &libp2p.LimitsConfigAndUsage{}
284
err := json.Unmarshal(b, limits)
285
require.NoError(t, err)
286
return limits
test/sharness/t0060-daemon.sh
+2
-3
@@ -81,12 +81,11 @@ test_expect_success "ipfs daemon output looks good" '
81
echo "Initializing daemon..." >expected_daemon &&
82
ipfs version --all >> expected_daemon &&
83
echo "" >>expected_daemon &&
84
- echo "Computing default go-libp2p Resource Manager limits based on:" >>expected_daemon &&
84
+ echo "Computed default go-libp2p Resource Manager limits based on:" >>expected_daemon &&
85
echo " - '"'"'Swarm.ResourceMgr.MaxMemory'"'"': \"4GB\"" >>expected_daemon &&
86
echo " - '"'"'Swarm.ResourceMgr.MaxFileDescriptors'"'"': 1024" >>expected_daemon &&
87
echo "" >>expected_daemon &&
88
- echo "Applying any user-supplied overrides on top." >>expected_daemon &&
89
- echo "Run '"'"'ipfs swarm limit all'"'"' to see the resulting limits." >>expected_daemon &&
88
+ echo "Theses can be inspected with '"'"'ipfs swarm resources'"'"'." >>expected_daemon &&
89
echo "" >>expected_daemon &&
90
sed "s/^/Swarm listening on /" listen_addrs >>expected_daemon &&
91
sed "s/^/Swarm announcing /" local_addrs >>expected_daemon &&