@cryptotaxi247 / kubo / commits / 798619641

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 &&