Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
626
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/README.md
generated
vendored
Normal file
626
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/README.md
generated
vendored
Normal file
@@ -0,0 +1,626 @@
|
||||
# The libp2p Network Resource Manager
|
||||
|
||||
This package contains the canonical implementation of the libp2p
|
||||
Network Resource Manager interface.
|
||||
|
||||
The implementation is based on the concept of Resource Management
|
||||
Scopes, whereby resource usage is constrained by a DAG of scopes,
|
||||
accounting for multiple levels of resource constraints.
|
||||
|
||||
The Resource Manager doesn't prioritize resource requests at all, it simply
|
||||
checks if the resource being requested is currently below the defined limits and
|
||||
returns an error if the limit is reached. It has no notion of honest vs bad peers.
|
||||
|
||||
The Resource Manager does have a special notion of [allowlisted](#allowlisting-multiaddrs-to-mitigate-eclipse-attacks) multiaddrs that
|
||||
have their own limits if the normal system limits are reached.
|
||||
|
||||
## Usage
|
||||
|
||||
The Resource Manager is intended to be used with go-libp2p. go-libp2p sets up a
|
||||
resource manager with the default autoscaled limits if none is provided, but if
|
||||
you want to configure things or if you want to enable metrics you'll use the
|
||||
resource manager like so:
|
||||
|
||||
```go
|
||||
// Start with the default scaling limits.
|
||||
scalingLimits := rcmgr.DefaultLimits
|
||||
|
||||
// Add limits around included libp2p protocols
|
||||
libp2p.SetDefaultServiceLimits(&scalingLimits)
|
||||
|
||||
// Turn the scaling limits into a concrete set of limits using `.AutoScale`. This
|
||||
// scales the limits proportional to your system memory.
|
||||
scaledDefaultLimits := scalingLimits.AutoScale()
|
||||
|
||||
// Tweak certain settings
|
||||
cfg := rcmgr.PartialLimitConfig{
|
||||
System: rcmgr.ResourceLimits{
|
||||
// Allow unlimited outbound streams
|
||||
StreamsOutbound: rcmgr.Unlimited,
|
||||
},
|
||||
// Everything else is default. The exact values will come from `scaledDefaultLimits` above.
|
||||
}
|
||||
|
||||
// Create our limits by using our cfg and replacing the default values with values from `scaledDefaultLimits`
|
||||
limits := cfg.Build(scaledDefaultLimits)
|
||||
|
||||
// The resource manager expects a limiter, se we create one from our limits.
|
||||
limiter := rcmgr.NewFixedLimiter(limits)
|
||||
|
||||
// Metrics are enabled by default. If you want to disable metrics, use the
|
||||
// WithMetricsDisabled option
|
||||
// Initialize the resource manager
|
||||
rm, err := rcmgr.NewResourceManager(limiter, rcmgr.WithMetricsDisabled())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create a libp2p host
|
||||
host, err := libp2p.New(libp2p.ResourceManager(rm))
|
||||
```
|
||||
|
||||
### Saving the limits config
|
||||
The easiest way to save the defined limits is to serialize the `PartialLimitConfig`
|
||||
type as JSON.
|
||||
|
||||
```go
|
||||
noisyNeighbor, _ := peer.Decode("QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf")
|
||||
cfg := rcmgr.PartialLimitConfig{
|
||||
System: &rcmgr.ResourceLimits{
|
||||
// Allow unlimited outbound streams
|
||||
StreamsOutbound: rcmgr.Unlimited,
|
||||
},
|
||||
Peer: map[peer.ID]rcmgr.ResourceLimits{
|
||||
noisyNeighbor: {
|
||||
// No inbound connections from this peer
|
||||
ConnsInbound: rcmgr.BlockAllLimit,
|
||||
// But let me open connections to them
|
||||
Conns: rcmgr.DefaultLimit,
|
||||
ConnsOutbound: rcmgr.DefaultLimit,
|
||||
// No inbound streams from this peer
|
||||
StreamsInbound: rcmgr.BlockAllLimit,
|
||||
// And let me open unlimited (by me) outbound streams (the peer may have their own limits on me)
|
||||
StreamsOutbound: rcmgr.Unlimited,
|
||||
},
|
||||
},
|
||||
}
|
||||
jsonBytes, _ := json.Marshal(&cfg)
|
||||
|
||||
// string(jsonBytes)
|
||||
// {
|
||||
// "System": {
|
||||
// "StreamsOutbound": "unlimited"
|
||||
// },
|
||||
// "Peer": {
|
||||
// "QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf": {
|
||||
// "StreamsInbound": "blockAll",
|
||||
// "StreamsOutbound": "unlimited",
|
||||
// "ConnsInbound": "blockAll"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
```
|
||||
|
||||
This will omit defaults from the JSON output. It will also serialize the
|
||||
blockAll, and unlimited values explicitly.
|
||||
|
||||
The `Memory` field is serialized as a string to workaround the JSON limitation
|
||||
of 32 bit integers (`Memory` is an int64).
|
||||
|
||||
## Basic Resources
|
||||
|
||||
### Memory
|
||||
|
||||
Perhaps the most fundamental resource is memory, and in particular
|
||||
buffers used for network operations. The system must provide an
|
||||
interface for components to reserve memory that accounts for buffers
|
||||
(and possibly other live objects), which is scoped within the component.
|
||||
Before a new buffer is allocated, the component should try a memory
|
||||
reservation, which can fail if the resource limit is exceeded. It is
|
||||
then up to the component to react to the error condition, depending on
|
||||
the situation. For example, a muxer failing to grow a buffer in
|
||||
response to a window change should simply retain the old buffer and
|
||||
operate at perhaps degraded performance.
|
||||
|
||||
### File Descriptors
|
||||
|
||||
File descriptors are an important resource that uses memory (and
|
||||
computational time) at the system level. They are also a scarce
|
||||
resource, as typically (unless the user explicitly intervenes) they
|
||||
are constrained by the system. Exhaustion of file descriptors may
|
||||
render the application incapable of operating (e.g., because it is
|
||||
unable to open a file). This is important for libp2p because most
|
||||
operating systems represent sockets as file descriptors.
|
||||
|
||||
### Connections
|
||||
|
||||
Connections are a higher-level concept endemic to libp2p; in order to
|
||||
communicate with another peer, a connection must first be
|
||||
established. Connections are an important resource in libp2p, as they
|
||||
consume memory, goroutines, and possibly file descriptors.
|
||||
|
||||
We distinguish between inbound and outbound connections, as the former
|
||||
are initiated by remote peers and consume resources in response to
|
||||
network events and thus need to be tightly controlled in order to
|
||||
protect the application from overload or attack. Outbound
|
||||
connections are typically initiated by the application's volition and
|
||||
don't need to be controlled as tightly. However, outbound connections
|
||||
still consume resources and may be initiated in response to network
|
||||
events because of (potentially faulty) application logic, so they
|
||||
still need to be constrained.
|
||||
|
||||
### Streams
|
||||
|
||||
Streams are the fundamental object of interaction in libp2p; all
|
||||
protocol interactions happen through a stream that goes over some
|
||||
connection. Streams are a fundamental resource in libp2p, as they
|
||||
consume memory and goroutines at all levels of the stack.
|
||||
|
||||
Streams always belong to a peer, specify a protocol and they may
|
||||
belong to some service in the system. Hence, this suggests that apart
|
||||
from global limits, we can constrain stream usage at finer
|
||||
granularity, at the protocol and service level.
|
||||
|
||||
Once again, we disinguish between inbound and outbound streams.
|
||||
Inbound streams are initiated by remote peers and consume resources in
|
||||
response to network events; controlling inbound stream usage is again
|
||||
paramount for protecting the system from overload or attack.
|
||||
Outbound streams are normally initiated by the application or some
|
||||
service in the system in order to effect some protocol
|
||||
interaction. However, they can also be initiated in response to
|
||||
network events because of application or service logic, so we still
|
||||
need to constrain them.
|
||||
|
||||
|
||||
## Resource Scopes
|
||||
|
||||
The Resource Manager is based on the concept of resource
|
||||
scopes. Resource Scopes account for resource usage that is temporally
|
||||
delimited for the span of the scope. Resource Scopes conceptually
|
||||
form a DAG, providing us with a mechanism to enforce multiresolution
|
||||
resource accounting. Downstream resource usage is aggregated at scopes
|
||||
higher up the graph.
|
||||
|
||||
The following diagram depicts the canonical scope graph:
|
||||
```
|
||||
System
|
||||
+------------> Transient.............+................+
|
||||
| . .
|
||||
+------------> Service------------- . ----------+ .
|
||||
| . | .
|
||||
+-------------> Protocol----------- . ----------+ .
|
||||
| . | .
|
||||
+-------------->* Peer \/ | .
|
||||
+------------> Connection | .
|
||||
| \/ \/
|
||||
+---------------------------> Stream
|
||||
```
|
||||
|
||||
### The System Scope
|
||||
|
||||
The system scope is the top level scope that accounts for global
|
||||
resource usage at all levels of the system. This scope nests and
|
||||
constrains all other scopes and institutes global hard limits.
|
||||
|
||||
### The Transient Scope
|
||||
|
||||
The transient scope accounts for resources that are in the process of
|
||||
full establishment. For instance, a new connection prior to the
|
||||
handshake does not belong to any peer, but it still needs to be
|
||||
constrained as this opens an avenue for attacks in transient resource
|
||||
usage. Similarly, a stream that has not negotiated a protocol yet is
|
||||
constrained by the transient scope.
|
||||
|
||||
The transient scope effectively represents a DMZ (DeMilitarized Zone),
|
||||
where resource usage can be accounted for connections and streams that
|
||||
are not fully established.
|
||||
|
||||
### The Allowlist System Scope
|
||||
|
||||
Same as the normal system scope above, but is used if the normal system scope is
|
||||
already at its limits and the resource is from an allowlisted peer. See
|
||||
[Allowlisting multiaddrs to mitigate eclipse
|
||||
attacks](#allowlisting-multiaddrs-to-mitigate-eclipse-attacks) see for more
|
||||
information.
|
||||
|
||||
### The Allowlist Transient Scope
|
||||
|
||||
Same as the normal transient scope above, but is used if the normal transient
|
||||
scope is already at its limits and the resource is from an allowlisted peer. See
|
||||
[Allowlisting multiaddrs to mitigate eclipse
|
||||
attacks](#allowlisting-multiaddrs-to-mitigate-eclipse-attacks) see for more
|
||||
information.
|
||||
|
||||
### Service Scopes
|
||||
|
||||
The system is typically organized across services, which may be
|
||||
ambient and provide basic functionality to the system (e.g. identify,
|
||||
autonat, relay, etc). Alternatively, services may be explicitly
|
||||
instantiated by the application, and provide core components of its
|
||||
functionality (e.g. pubsub, the DHT, etc).
|
||||
|
||||
Services are logical groupings of streams that implement protocol flow
|
||||
and may additionally consume resources such as memory. Services
|
||||
typically have at least one stream handler, so they are subject to
|
||||
inbound stream creation and resource usage in response to network
|
||||
events. As such, the system explicitly models them allowing for
|
||||
isolated resource usage that can be tuned by the user.
|
||||
|
||||
### Protocol Scopes
|
||||
|
||||
Protocol Scopes account for resources at the protocol level. They are
|
||||
an intermediate resource scope which can constrain streams which may
|
||||
not have a service associated or for resource control within a
|
||||
service. It also provides an opportunity for system operators to
|
||||
explicitly restrict specific protocols.
|
||||
|
||||
For instance, a service that is not aware of the resource manager and
|
||||
has not been ported to mark its streams, may still gain limits
|
||||
transparently without any programmer intervention. Furthermore, the
|
||||
protocol scope can constrain resource usage for services that
|
||||
implement multiple protocols for the sake of backwards
|
||||
compatibility. A tighter limit in some older protocol can protect the
|
||||
application from resource consumption caused by legacy clients or
|
||||
potential attacks.
|
||||
|
||||
For a concrete example, consider pubsub with the gossipsub router: the
|
||||
service also understands the floodsub protocol for backwards
|
||||
compatibility and support for unsophisticated clients that are lagging
|
||||
in the implementation effort. By specifying a lower limit for the
|
||||
floodsub protocol, we can can constrain the service level for legacy
|
||||
clients using an inefficient protocol.
|
||||
|
||||
### Peer Scopes
|
||||
|
||||
The peer scope accounts for resource usage by an individual peer. This
|
||||
constrains connections and streams and limits the blast radius of
|
||||
resource consumption by a single remote peer.
|
||||
|
||||
This ensures that no single peer can use more resources than allowed
|
||||
by the peer limits. Every peer has a default limit, but the programmer
|
||||
may raise (or lower) limits for specific peers.
|
||||
|
||||
|
||||
### Connection Scopes
|
||||
|
||||
The connection scope is delimited to the duration of a connection and
|
||||
constrains resource usage by a single connection. The scope is a leaf
|
||||
in the DAG, with a span that begins when a connection is established
|
||||
and ends when the connection is closed. Its resources are aggregated
|
||||
to the resource usage of a peer.
|
||||
|
||||
### Stream Scopes
|
||||
|
||||
The stream scope is delimited to the duration of a stream, and
|
||||
constrains resource usage by a single stream. This scope is also a
|
||||
leaf in the DAG, with span that begins when a stream is created and
|
||||
ends when the stream is closed. Its resources are aggregated to the
|
||||
resource usage of a peer, and constrained by a service and protocol
|
||||
scope.
|
||||
|
||||
### User Transaction Scopes
|
||||
|
||||
User transaction scopes can be created as a child of any extant
|
||||
resource scope, and provide the programmer with a delimited scope for
|
||||
easy resource accounting. Transactions may form a tree that is rooted
|
||||
to some canonical scope in the scope DAG.
|
||||
|
||||
For instance, a programmer may create a transaction scope within a
|
||||
service that accounts for some control flow delimited resource
|
||||
usage. Similarly, a programmer may create a transaction scope for some
|
||||
interaction within a stream, e.g. a Request/Response interaction that
|
||||
uses a buffer.
|
||||
|
||||
## Limits
|
||||
|
||||
Each resource scope has an associated limit object, which designates
|
||||
limits for all [basic resources](#basic-resources). The limit is checked every time some
|
||||
resource is reserved and provides the system with an opportunity to
|
||||
constrain resource usage.
|
||||
|
||||
There are separate limits for each class of scope, allowing for
|
||||
multiresolution and aggregate resource accounting. As such, we have
|
||||
limits for the system and transient scopes, default and specific
|
||||
limits for services, protocols, and peers, and limits for connections
|
||||
and streams.
|
||||
|
||||
### Scaling Limits
|
||||
|
||||
When building software that is supposed to run on many different kind of machines,
|
||||
with various memory and CPU configurations, it is desirable to have limits that
|
||||
scale with the size of the machine.
|
||||
|
||||
This is done using the `ScalingLimitConfig`. For every scope, this configuration
|
||||
struct defines the absolutely bare minimum limits, and an (optional) increase of
|
||||
these limits, which will be applied on nodes that have sufficient memory.
|
||||
|
||||
A `ScalingLimitConfig` can be converted into a `ConcreteLimitConfig` (which can then be
|
||||
used to initialize a fixed limiter with `NewFixedLimiter`) by calling the `Scale` method.
|
||||
The `Scale` method takes two parameters: the amount of memory and the number of file
|
||||
descriptors that an application is willing to dedicate to libp2p.
|
||||
|
||||
These amounts will differ between use cases. A blockchain node running on a dedicated
|
||||
server might have a lot of memory, and dedicate 1/4 of that memory to libp2p. On the
|
||||
other end of the spectrum, a desktop companion application running as a background
|
||||
task on a consumer laptop will probably dedicate significantly less than 1/4 of its system
|
||||
memory to libp2p.
|
||||
|
||||
For convenience, the `ScalingLimitConfig` also provides an `AutoScale` method,
|
||||
which determines the amount of memory and file descriptors available on the
|
||||
system, and dedicates up to 1/8 of the memory and 1/2 of the file descriptors to
|
||||
libp2p.
|
||||
|
||||
For example, one might set:
|
||||
```go
|
||||
var scalingLimits = ScalingLimitConfig{
|
||||
SystemBaseLimit: BaseLimit{
|
||||
ConnsInbound: 64,
|
||||
ConnsOutbound: 128,
|
||||
Conns: 128,
|
||||
StreamsInbound: 512,
|
||||
StreamsOutbound: 1024,
|
||||
Streams: 1024,
|
||||
Memory: 128 << 20,
|
||||
FD: 256,
|
||||
},
|
||||
SystemLimitIncrease: BaseLimitIncrease{
|
||||
ConnsInbound: 32,
|
||||
ConnsOutbound: 64,
|
||||
Conns: 64,
|
||||
StreamsInbound: 256,
|
||||
StreamsOutbound: 512,
|
||||
Streams: 512,
|
||||
Memory: 256 << 20,
|
||||
FDFraction: 1,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The base limit (`SystemBaseLimit`) here is the minimum configuration that any
|
||||
node will have, no matter how little memory it possesses. For every GB of memory
|
||||
passed into the `Scale` method, an increase of (`SystemLimitIncrease`) is added.
|
||||
|
||||
For Example, calling `Scale` with 4 GB of memory will result in a limit of 384 for
|
||||
`Conns` (128 + 4*64).
|
||||
|
||||
The `FDFraction` defines how many of the file descriptors are allocated to this
|
||||
scope. In the example above, when called with a file descriptor value of 1000,
|
||||
this would result in a limit of 1000 (1000 * 1) file descriptors for the system
|
||||
scope. See `TestReadmeExample` in `limit_test.go`.
|
||||
|
||||
Note that we only showed the configuration for the system scope here, equivalent
|
||||
configuration options apply to all other scopes as well.
|
||||
|
||||
### Default limits
|
||||
|
||||
By default the resource manager ships with some reasonable scaling limits and
|
||||
makes a reasonable guess at how much system memory you want to dedicate to the
|
||||
go-libp2p process. For the default definitions see [`DefaultLimits` and
|
||||
`ScalingLimitConfig.AutoScale()`](./limit_defaults.go).
|
||||
|
||||
### Tweaking Defaults
|
||||
|
||||
If the defaults seem mostly okay, but you want to adjust one facet you can
|
||||
simply copy the default struct object and update the field you want to change. You can
|
||||
apply changes to a `BaseLimit`, `BaseLimitIncrease`, and `ConcreteLimitConfig` with
|
||||
`.Apply`.
|
||||
|
||||
Example
|
||||
```
|
||||
// An example on how to tweak the default limits
|
||||
tweakedDefaults := DefaultLimits
|
||||
tweakedDefaults.ProtocolBaseLimit.Streams = 1024
|
||||
tweakedDefaults.ProtocolBaseLimit.StreamsInbound = 512
|
||||
tweakedDefaults.ProtocolBaseLimit.StreamsOutbound = 512
|
||||
```
|
||||
|
||||
### How to tune your limits
|
||||
|
||||
Once you've set your limits and monitoring (see [Monitoring](#monitoring) below)
|
||||
you can now tune your limits better. The `rcmgr_blocked_resources` metric will
|
||||
tell you what was blocked and for what scope. If you see a steady stream of
|
||||
these blocked requests it means your resource limits are too low for your usage.
|
||||
If you see a rare sudden spike, this is okay and it means the resource manager
|
||||
protected you from some anomaly.
|
||||
|
||||
### How to disable limits
|
||||
|
||||
Sometimes disabling all limits is useful when you want to see how much
|
||||
resources you use during normal operation. You can then use this information to
|
||||
define your initial limits. Disable the limits by using `InfiniteLimits`.
|
||||
|
||||
### Debug "resource limit exceeded" errors
|
||||
|
||||
These errors occur whenever a limit is hit. For example, you'll get this error if
|
||||
you are at your limit for the number of streams you can have, and you try to
|
||||
open one more.
|
||||
|
||||
Example Log:
|
||||
```
|
||||
2022-08-12T15:49:35.459-0700 DEBUG rcmgr go-libp2p-resource-manager@v0.5.3/scope.go:541 blocked connection from constraining edge {"scope": "conn-19667", "edge": "system", "direction": "Inbound", "usefd": false, "current": 100, "attempted": 1, "limit": 100, "stat": {"NumStreamsInbound":28,"NumStreamsOutbound":66,"NumConnsInbound":37,"NumConnsOutbound":63,"NumFD":33,"Memory":8687616}, "error": "system: cannot reserve connection: resource limit exceeded"}
|
||||
```
|
||||
|
||||
The log line above is an example log line that gets emitted if you enable debug
|
||||
logging in the resource manager. You can do this by setting the environment
|
||||
variable `GOLOG_LOG_LEVEL="rcmgr=debug"`. By default only the error is
|
||||
returned to the caller, and nothing is logged by the resource manager itself.
|
||||
|
||||
The log line message (and returned error) will tell you which resource limit was
|
||||
hit (connection in the log above) and what blocked it (in this case it was the
|
||||
system scope that blocked it). The log will also include some more information
|
||||
about the current usage of the resources. In the example log above, there is a
|
||||
limit of 100 connections, and you can see that we have 37 inbound connections
|
||||
and 63 outbound connections. We've reached the limit and the resource manager
|
||||
will block any further connections.
|
||||
|
||||
The next step in debugging is seeing if this is a recurring problem or just a
|
||||
transient error. If it's a transient error it's okay to ignore it since the
|
||||
resource manager was doing its job in keeping resource usage under the limit. If
|
||||
it's recurring then you should understand what's causing you to hit these limits
|
||||
and either refactor your application or raise the limits.
|
||||
|
||||
To check if it's a recurring problem you can count the number of times you've
|
||||
seen the `"resource limit exceeded"` error over time. You can also check the
|
||||
`rcmgr_blocked_resources` metric to see how many times the resource manager has
|
||||
blocked a resource over time.
|
||||
|
||||

|
||||
|
||||
If the resource is blocked by a protocol-level scope, take a look at the various
|
||||
resource usages in the metrics. For example, if you run into a new stream being blocked,
|
||||
you can check the
|
||||
`rcmgr_streams` metric and the "Streams by protocol" graph in the Grafana
|
||||
dashboard (assuming you've set that up or something similar – see
|
||||
[Monitoring](#monitoring)) to understand the usage pattern of that specific
|
||||
protocol. This can help answer questions such as: "Am I constantly around my
|
||||
limit?", "Does it make sense to raise my limit?", "Are there any patterns around
|
||||
hitting this limit?", and "should I refactor my protocol implementation?"
|
||||
|
||||
## Monitoring
|
||||
|
||||
Once you have limits set, you'll want to monitor to see if you're running into
|
||||
your limits often. This could be a sign that you need to raise your limits
|
||||
(your process is more intensive than you originally thought) or that you need
|
||||
to fix something in your application (surely you don't need over 1000 streams?).
|
||||
|
||||
There are Prometheus metrics that can be hooked up to the resource manager. See
|
||||
`obs/stats_test.go` for an example on how to enable this, and `DefaultViews` in
|
||||
`stats.go` for recommended views. These metrics can be hooked up to Prometheus
|
||||
or any other platform that can scrape a prometheus endpoint.
|
||||
|
||||
There is also an included Grafana dashboard to help kickstart your
|
||||
observability into the resource manager. Find more information about it at
|
||||
[here](./../../../dashboards/resource-manager/README.md).
|
||||
|
||||
## Allowlisting multiaddrs to mitigate eclipse attacks
|
||||
|
||||
If you have a set of trusted peers and IP addresses, you can use the resource
|
||||
manager's [Allowlist](./docs/allowlist.md) to protect yourself from eclipse
|
||||
attacks. The set of peers in the allowlist will have their own limits in case
|
||||
the normal limits are reached. This means you will always be able to connect to
|
||||
these trusted peers even if you've already reached your system limits.
|
||||
|
||||
Look at `WithAllowlistedMultiaddrs` and its example in the GoDoc to learn more.
|
||||
|
||||
## ConnManager vs Resource Manager
|
||||
|
||||
go-libp2p already includes a [connection
|
||||
manager](https://pkg.go.dev/github.com/libp2p/go-libp2p/core/connmgr#ConnManager),
|
||||
so what's the difference between the `ConnManager` and the `ResourceManager`?
|
||||
|
||||
ConnManager:
|
||||
1. Configured with a low and high watermark number of connections.
|
||||
2. Attempts to maintain the number of connections between the low and high
|
||||
markers.
|
||||
3. Connections can be given metadata and weight (e.g. a hole punched
|
||||
connection is more valuable than a connection to a publicly addressable
|
||||
endpoint since it took more effort to make the hole punched connection).
|
||||
4. The ConnManager will trim connections once the high watermark is reached. and
|
||||
trim down to the low watermark.
|
||||
5. Won't block adding another connection above the high watermark, but will
|
||||
trigger the trim mentioned above.
|
||||
6. Can trim and prioritize connections with custom logic.
|
||||
7. No concept of scopes (like the resource manager).
|
||||
|
||||
Resource Manager:
|
||||
1. Configured with limits on the number of outgoing and incoming connections at
|
||||
different [resource scopes](#resource-scopes).
|
||||
2. Will block adding any more connections if any of the scope-specific limits would be exceeded.
|
||||
|
||||
The natural question when comparing these two managers is "how do the watermarks
|
||||
and limits interact with each other?". The short answer is that they don't know
|
||||
about each other. This can lead to some surprising subtleties, such as the
|
||||
trimming never happening because the resource manager's limit is lower than the
|
||||
high watermark. This is confusing, and we'd like to fix it. The issue is
|
||||
captured in [go-libp2p#1640](https://github.com/libp2p/go-libp2p/issues/1640).
|
||||
|
||||
When configuring the resource manager and connection manager, you should set the
|
||||
limits in the resource manager as your hard limits that you would never want to
|
||||
go over, and set the low/high watermarks as the range at which your application
|
||||
works best.
|
||||
|
||||
## Examples
|
||||
|
||||
Here we consider some concrete examples that can elucidate the abstract
|
||||
design as described so far.
|
||||
|
||||
### Stream Lifetime
|
||||
|
||||
Let's consider a stream and the limits that apply to it.
|
||||
When the stream scope is first opened, it is created by calling
|
||||
`ResourceManager.OpenStream`.
|
||||
|
||||
Initially the stream is constrained by:
|
||||
- the system scope, where global hard limits apply.
|
||||
- the transient scope, where unnegotiated streams live.
|
||||
- the peer scope, where the limits for the peer at the other end of the stream
|
||||
apply.
|
||||
|
||||
Once the protocol has been negotiated, the protocol is set by calling
|
||||
`StreamManagementScope.SetProtocol`. The constraint from the
|
||||
transient scope is removed and the stream is now constrained by the
|
||||
protocol instead.
|
||||
|
||||
More specifically, the following constraints apply:
|
||||
- the system scope, where global hard limits apply.
|
||||
- the peer scope, where the limits for the peer at the other end of the stream
|
||||
apply.
|
||||
- the protocol scope, where the limits of the specific protocol used apply.
|
||||
|
||||
The existence of the protocol limit allows us to implicitly constrain
|
||||
streams for services that have not been ported to the resource manager
|
||||
yet. Once the programmer attaches a stream to a service by calling
|
||||
`StreamScope.SetService`, the stream resources are aggregated and constrained
|
||||
by the service scope in addition to its protocol scope.
|
||||
|
||||
More specifically the following constraints apply:
|
||||
- the system scope, where global hard limits apply.
|
||||
- the peer scope, where the limits for the peer at the other end of the stream
|
||||
apply.
|
||||
- the service scope, where the limits of the specific service owning the stream apply.
|
||||
- the protocol scope, where the limits of the specific protocol for the stream apply.
|
||||
|
||||
|
||||
The resource transfer that happens in the `SetProtocol` and `SetService`
|
||||
gives the opportunity to the resource manager to gate the streams. If
|
||||
the transfer results in exceeding the scope limits, then a error
|
||||
indicating "resource limit exceeded" is returned. The wrapped error
|
||||
includes the name of the scope rejecting the resource acquisition to
|
||||
aid understanding of applicable limits. Note that the (wrapped) error
|
||||
implements `net.Error` and is marked as temporary, so that the
|
||||
programmer can handle by backoff retry.
|
||||
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The package only exports a constructor for the resource manager and
|
||||
basic types for defining limits. Internals are not exposed.
|
||||
- Internally, there is a resources object that is embedded in every scope and
|
||||
implements resource accounting.
|
||||
- There is a single implementation of a generic resource scope, that
|
||||
provides all necessary interface methods.
|
||||
- There are concrete types for all canonical scopes, embedding a
|
||||
pointer to a generic resource scope.
|
||||
- Peer and Protocol scopes, which may be created in response to
|
||||
network events, are periodically garbage collected.
|
||||
|
||||
## Design Considerations
|
||||
|
||||
- The Resource Manager must account for basic resource usage at all
|
||||
levels of the stack, from the internals to application components
|
||||
that use the network facilities of libp2p.
|
||||
- Basic resources include memory, streams, connections, and file
|
||||
descriptors. These account for both space and time used by
|
||||
the stack, as each resource has a direct effect on the system
|
||||
availability and performance.
|
||||
- The design must support seamless integration for user applications,
|
||||
which should reap the benefits of resource management without any
|
||||
changes. That is, existing applications should be oblivious of the
|
||||
resource manager and transparently obtain limits which protects it
|
||||
from resource exhaustion and OOM conditions.
|
||||
- At the same time, the design must support opt-in resource usage
|
||||
accounting for applications that want to explicitly utilize the
|
||||
facilities of the system to inform about and constrain their own
|
||||
resource usage.
|
||||
- The design must allow the user to set their own limits, which can be
|
||||
static (fixed) or dynamic.
|
||||
216
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/allowlist.go
generated
vendored
Normal file
216
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/allowlist.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
)
|
||||
|
||||
type Allowlist struct {
|
||||
mu sync.RWMutex
|
||||
// a simple structure of lists of networks. There is probably a faster way
|
||||
// to check if an IP address is in this network than iterating over this
|
||||
// list, but this is good enough for small numbers of networks (<1_000).
|
||||
// Analyze the benchmark before trying to optimize this.
|
||||
|
||||
// Any peer with these IPs are allowed
|
||||
allowedNetworks []*net.IPNet
|
||||
|
||||
// Only the specified peers can use these IPs
|
||||
allowedPeerByNetwork map[peer.ID][]*net.IPNet
|
||||
}
|
||||
|
||||
// WithAllowlistedMultiaddrs sets the multiaddrs to be in the allowlist
|
||||
func WithAllowlistedMultiaddrs(mas []multiaddr.Multiaddr) Option {
|
||||
return func(rm *resourceManager) error {
|
||||
for _, ma := range mas {
|
||||
err := rm.allowlist.Add(ma)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func newAllowlist() Allowlist {
|
||||
return Allowlist{
|
||||
allowedPeerByNetwork: make(map[peer.ID][]*net.IPNet),
|
||||
}
|
||||
}
|
||||
|
||||
func toIPNet(ma multiaddr.Multiaddr) (*net.IPNet, peer.ID, error) {
|
||||
var ipString string
|
||||
var mask string
|
||||
var allowedPeerStr string
|
||||
var allowedPeer peer.ID
|
||||
var isIPV4 bool
|
||||
|
||||
multiaddr.ForEach(ma, func(c multiaddr.Component) bool {
|
||||
if c.Protocol().Code == multiaddr.P_IP4 || c.Protocol().Code == multiaddr.P_IP6 {
|
||||
isIPV4 = c.Protocol().Code == multiaddr.P_IP4
|
||||
ipString = c.Value()
|
||||
}
|
||||
if c.Protocol().Code == multiaddr.P_IPCIDR {
|
||||
mask = c.Value()
|
||||
}
|
||||
if c.Protocol().Code == multiaddr.P_P2P {
|
||||
allowedPeerStr = c.Value()
|
||||
}
|
||||
return ipString == "" || mask == "" || allowedPeerStr == ""
|
||||
})
|
||||
|
||||
if ipString == "" {
|
||||
return nil, allowedPeer, errors.New("missing ip address")
|
||||
}
|
||||
|
||||
if allowedPeerStr != "" {
|
||||
var err error
|
||||
allowedPeer, err = peer.Decode(allowedPeerStr)
|
||||
if err != nil {
|
||||
return nil, allowedPeer, fmt.Errorf("failed to decode allowed peer: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if mask == "" {
|
||||
ip := net.ParseIP(ipString)
|
||||
if ip == nil {
|
||||
return nil, allowedPeer, errors.New("invalid ip address")
|
||||
}
|
||||
var mask net.IPMask
|
||||
if isIPV4 {
|
||||
mask = net.CIDRMask(32, 32)
|
||||
} else {
|
||||
mask = net.CIDRMask(128, 128)
|
||||
}
|
||||
|
||||
net := &net.IPNet{IP: ip, Mask: mask}
|
||||
return net, allowedPeer, nil
|
||||
}
|
||||
|
||||
_, ipnet, err := net.ParseCIDR(ipString + "/" + mask)
|
||||
return ipnet, allowedPeer, err
|
||||
|
||||
}
|
||||
|
||||
// Add takes a multiaddr and adds it to the allowlist. The multiaddr should be
|
||||
// an ip address of the peer with or without a `/p2p` protocol.
|
||||
// e.g. /ip4/1.2.3.4/p2p/QmFoo, /ip4/1.2.3.4, and /ip4/1.2.3.0/ipcidr/24 are valid.
|
||||
// /p2p/QmFoo is not valid.
|
||||
func (al *Allowlist) Add(ma multiaddr.Multiaddr) error {
|
||||
ipnet, allowedPeer, err := toIPNet(ma)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
al.mu.Lock()
|
||||
defer al.mu.Unlock()
|
||||
|
||||
if allowedPeer != peer.ID("") {
|
||||
// We have a peerID constraint
|
||||
if al.allowedPeerByNetwork == nil {
|
||||
al.allowedPeerByNetwork = make(map[peer.ID][]*net.IPNet)
|
||||
}
|
||||
al.allowedPeerByNetwork[allowedPeer] = append(al.allowedPeerByNetwork[allowedPeer], ipnet)
|
||||
} else {
|
||||
al.allowedNetworks = append(al.allowedNetworks, ipnet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (al *Allowlist) Remove(ma multiaddr.Multiaddr) error {
|
||||
ipnet, allowedPeer, err := toIPNet(ma)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
al.mu.Lock()
|
||||
defer al.mu.Unlock()
|
||||
|
||||
ipNetList := al.allowedNetworks
|
||||
|
||||
if allowedPeer != "" {
|
||||
// We have a peerID constraint
|
||||
ipNetList = al.allowedPeerByNetwork[allowedPeer]
|
||||
}
|
||||
|
||||
if ipNetList == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := len(ipNetList)
|
||||
for i > 0 {
|
||||
i--
|
||||
if ipNetList[i].IP.Equal(ipnet.IP) && bytes.Equal(ipNetList[i].Mask, ipnet.Mask) {
|
||||
// swap remove
|
||||
ipNetList[i] = ipNetList[len(ipNetList)-1]
|
||||
ipNetList = ipNetList[:len(ipNetList)-1]
|
||||
// We only remove one thing
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allowedPeer != "" {
|
||||
al.allowedPeerByNetwork[allowedPeer] = ipNetList
|
||||
} else {
|
||||
al.allowedNetworks = ipNetList
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (al *Allowlist) Allowed(ma multiaddr.Multiaddr) bool {
|
||||
ip, err := manet.ToIP(ma)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
al.mu.RLock()
|
||||
defer al.mu.RUnlock()
|
||||
|
||||
for _, network := range al.allowedNetworks {
|
||||
if network.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, allowedNetworks := range al.allowedPeerByNetwork {
|
||||
for _, network := range allowedNetworks {
|
||||
if network.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (al *Allowlist) AllowedPeerAndMultiaddr(peerID peer.ID, ma multiaddr.Multiaddr) bool {
|
||||
ip, err := manet.ToIP(ma)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
al.mu.RLock()
|
||||
defer al.mu.RUnlock()
|
||||
|
||||
for _, network := range al.allowedNetworks {
|
||||
if network.Contains(ip) {
|
||||
// We found a match that isn't constrained by a peerID
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if expectedNetworks, ok := al.allowedPeerByNetwork[peerID]; ok {
|
||||
for _, expectedNetwork := range expectedNetworks {
|
||||
if expectedNetwork.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
81
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/error.go
generated
vendored
Normal file
81
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/error.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
type ErrStreamOrConnLimitExceeded struct {
|
||||
current, attempted, limit int
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ErrStreamOrConnLimitExceeded) Error() string { return e.err.Error() }
|
||||
func (e *ErrStreamOrConnLimitExceeded) Unwrap() error { return e.err }
|
||||
|
||||
// edge may be "" if this is not an edge error
|
||||
func logValuesStreamLimit(scope, edge string, dir network.Direction, stat network.ScopeStat, err error) []interface{} {
|
||||
logValues := make([]interface{}, 0, 2*8)
|
||||
logValues = append(logValues, "scope", scope)
|
||||
if edge != "" {
|
||||
logValues = append(logValues, "edge", edge)
|
||||
}
|
||||
logValues = append(logValues, "direction", dir)
|
||||
var e *ErrStreamOrConnLimitExceeded
|
||||
if errors.As(err, &e) {
|
||||
logValues = append(logValues,
|
||||
"current", e.current,
|
||||
"attempted", e.attempted,
|
||||
"limit", e.limit,
|
||||
)
|
||||
}
|
||||
return append(logValues, "stat", stat, "error", err)
|
||||
}
|
||||
|
||||
// edge may be "" if this is not an edge error
|
||||
func logValuesConnLimit(scope, edge string, dir network.Direction, usefd bool, stat network.ScopeStat, err error) []interface{} {
|
||||
logValues := make([]interface{}, 0, 2*9)
|
||||
logValues = append(logValues, "scope", scope)
|
||||
if edge != "" {
|
||||
logValues = append(logValues, "edge", edge)
|
||||
}
|
||||
logValues = append(logValues, "direction", dir, "usefd", usefd)
|
||||
var e *ErrStreamOrConnLimitExceeded
|
||||
if errors.As(err, &e) {
|
||||
logValues = append(logValues,
|
||||
"current", e.current,
|
||||
"attempted", e.attempted,
|
||||
"limit", e.limit,
|
||||
)
|
||||
}
|
||||
return append(logValues, "stat", stat, "error", err)
|
||||
}
|
||||
|
||||
type ErrMemoryLimitExceeded struct {
|
||||
current, attempted, limit int64
|
||||
priority uint8
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ErrMemoryLimitExceeded) Error() string { return e.err.Error() }
|
||||
func (e *ErrMemoryLimitExceeded) Unwrap() error { return e.err }
|
||||
|
||||
// edge may be "" if this is not an edge error
|
||||
func logValuesMemoryLimit(scope, edge string, stat network.ScopeStat, err error) []interface{} {
|
||||
logValues := make([]interface{}, 0, 2*8)
|
||||
logValues = append(logValues, "scope", scope)
|
||||
if edge != "" {
|
||||
logValues = append(logValues, "edge", edge)
|
||||
}
|
||||
var e *ErrMemoryLimitExceeded
|
||||
if errors.As(err, &e) {
|
||||
logValues = append(logValues,
|
||||
"current", e.current,
|
||||
"attempted", e.attempted,
|
||||
"priority", e.priority,
|
||||
"limit", e.limit,
|
||||
)
|
||||
}
|
||||
return append(logValues, "stat", stat, "error", err)
|
||||
}
|
||||
151
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/extapi.go
generated
vendored
Normal file
151
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/extapi.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
)
|
||||
|
||||
// ResourceScopeLimiter is a trait interface that allows you to access scope limits.
|
||||
type ResourceScopeLimiter interface {
|
||||
Limit() Limit
|
||||
SetLimit(Limit)
|
||||
}
|
||||
|
||||
var _ ResourceScopeLimiter = (*resourceScope)(nil)
|
||||
|
||||
// ResourceManagerStat is a trait that allows you to access resource manager state.
|
||||
type ResourceManagerState interface {
|
||||
ListServices() []string
|
||||
ListProtocols() []protocol.ID
|
||||
ListPeers() []peer.ID
|
||||
|
||||
Stat() ResourceManagerStat
|
||||
}
|
||||
|
||||
type ResourceManagerStat struct {
|
||||
System network.ScopeStat
|
||||
Transient network.ScopeStat
|
||||
Services map[string]network.ScopeStat
|
||||
Protocols map[protocol.ID]network.ScopeStat
|
||||
Peers map[peer.ID]network.ScopeStat
|
||||
}
|
||||
|
||||
var _ ResourceManagerState = (*resourceManager)(nil)
|
||||
|
||||
func (s *resourceScope) Limit() Limit {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
return s.rc.limit
|
||||
}
|
||||
|
||||
func (s *resourceScope) SetLimit(limit Limit) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.rc.limit = limit
|
||||
}
|
||||
|
||||
func (s *protocolScope) SetLimit(limit Limit) {
|
||||
s.rcmgr.setStickyProtocol(s.proto)
|
||||
s.resourceScope.SetLimit(limit)
|
||||
}
|
||||
|
||||
func (s *peerScope) SetLimit(limit Limit) {
|
||||
s.rcmgr.setStickyPeer(s.peer)
|
||||
s.resourceScope.SetLimit(limit)
|
||||
}
|
||||
|
||||
func (r *resourceManager) ListServices() []string {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
result := make([]string, 0, len(r.svc))
|
||||
for svc := range r.svc {
|
||||
result = append(result, svc)
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return strings.Compare(result[i], result[j]) < 0
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *resourceManager) ListProtocols() []protocol.ID {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
result := make([]protocol.ID, 0, len(r.proto))
|
||||
for p := range r.proto {
|
||||
result = append(result, p)
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i] < result[j]
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *resourceManager) ListPeers() []peer.ID {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
result := make([]peer.ID, 0, len(r.peer))
|
||||
for p := range r.peer {
|
||||
result = append(result, p)
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return bytes.Compare([]byte(result[i]), []byte(result[j])) < 0
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *resourceManager) Stat() (result ResourceManagerStat) {
|
||||
r.mx.Lock()
|
||||
svcs := make([]*serviceScope, 0, len(r.svc))
|
||||
for _, svc := range r.svc {
|
||||
svcs = append(svcs, svc)
|
||||
}
|
||||
protos := make([]*protocolScope, 0, len(r.proto))
|
||||
for _, proto := range r.proto {
|
||||
protos = append(protos, proto)
|
||||
}
|
||||
peers := make([]*peerScope, 0, len(r.peer))
|
||||
for _, peer := range r.peer {
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
r.mx.Unlock()
|
||||
|
||||
// Note: there is no global lock, so the system is updating while we are dumping its state...
|
||||
// as such stats might not exactly add up to the system level; we take the system stat
|
||||
// last nonetheless so that this is the most up-to-date snapshot
|
||||
result.Peers = make(map[peer.ID]network.ScopeStat, len(peers))
|
||||
for _, peer := range peers {
|
||||
result.Peers[peer.peer] = peer.Stat()
|
||||
}
|
||||
result.Protocols = make(map[protocol.ID]network.ScopeStat, len(protos))
|
||||
for _, proto := range protos {
|
||||
result.Protocols[proto.proto] = proto.Stat()
|
||||
}
|
||||
result.Services = make(map[string]network.ScopeStat, len(svcs))
|
||||
for _, svc := range svcs {
|
||||
result.Services[svc.service] = svc.Stat()
|
||||
}
|
||||
result.Transient = r.transient.Stat()
|
||||
result.System = r.system.Stat()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *resourceManager) GetConnLimit() int {
|
||||
return r.limits.GetConnLimits().GetConnTotalLimit()
|
||||
}
|
||||
297
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit.go
generated
vendored
Normal file
297
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit.go
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
Package rcmgr is the resource manager for go-libp2p. This allows you to track
|
||||
resources being used throughout your go-libp2p process. As well as making sure
|
||||
that the process doesn't use more resources than what you define as your
|
||||
limits. The resource manager only knows about things it is told about, so it's
|
||||
the responsibility of the user of this library (either go-libp2p or a go-libp2p
|
||||
user) to make sure they check with the resource manager before actually
|
||||
allocating the resource.
|
||||
*/
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
)
|
||||
|
||||
// Limit is an object that specifies basic resource limits.
|
||||
type Limit interface {
|
||||
// GetMemoryLimit returns the (current) memory limit.
|
||||
GetMemoryLimit() int64
|
||||
// GetStreamLimit returns the stream limit, for inbound or outbound streams.
|
||||
GetStreamLimit(network.Direction) int
|
||||
// GetStreamTotalLimit returns the total stream limit
|
||||
GetStreamTotalLimit() int
|
||||
// GetConnLimit returns the connection limit, for inbound or outbound connections.
|
||||
GetConnLimit(network.Direction) int
|
||||
// GetConnTotalLimit returns the total connection limit
|
||||
GetConnTotalLimit() int
|
||||
// GetFDLimit returns the file descriptor limit.
|
||||
GetFDLimit() int
|
||||
}
|
||||
|
||||
// Limiter is the interface for providing limits to the resource manager.
|
||||
type Limiter interface {
|
||||
GetSystemLimits() Limit
|
||||
GetTransientLimits() Limit
|
||||
GetAllowlistedSystemLimits() Limit
|
||||
GetAllowlistedTransientLimits() Limit
|
||||
GetServiceLimits(svc string) Limit
|
||||
GetServicePeerLimits(svc string) Limit
|
||||
GetProtocolLimits(proto protocol.ID) Limit
|
||||
GetProtocolPeerLimits(proto protocol.ID) Limit
|
||||
GetPeerLimits(p peer.ID) Limit
|
||||
GetStreamLimits(p peer.ID) Limit
|
||||
GetConnLimits() Limit
|
||||
}
|
||||
|
||||
// NewDefaultLimiterFromJSON creates a new limiter by parsing a json configuration,
|
||||
// using the default limits for fallback.
|
||||
func NewDefaultLimiterFromJSON(in io.Reader) (Limiter, error) {
|
||||
return NewLimiterFromJSON(in, DefaultLimits.AutoScale())
|
||||
}
|
||||
|
||||
// NewLimiterFromJSON creates a new limiter by parsing a json configuration.
|
||||
func NewLimiterFromJSON(in io.Reader, defaults ConcreteLimitConfig) (Limiter, error) {
|
||||
cfg, err := readLimiterConfigFromJSON(in, defaults)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fixedLimiter{cfg}, nil
|
||||
}
|
||||
|
||||
func readLimiterConfigFromJSON(in io.Reader, defaults ConcreteLimitConfig) (ConcreteLimitConfig, error) {
|
||||
var cfg PartialLimitConfig
|
||||
if err := json.NewDecoder(in).Decode(&cfg); err != nil {
|
||||
return ConcreteLimitConfig{}, err
|
||||
}
|
||||
return cfg.Build(defaults), nil
|
||||
}
|
||||
|
||||
// fixedLimiter is a limiter with fixed limits.
|
||||
type fixedLimiter struct {
|
||||
ConcreteLimitConfig
|
||||
}
|
||||
|
||||
var _ Limiter = (*fixedLimiter)(nil)
|
||||
|
||||
func NewFixedLimiter(conf ConcreteLimitConfig) Limiter {
|
||||
log.Debugw("initializing new limiter with config", "limits", conf)
|
||||
return &fixedLimiter{conf}
|
||||
}
|
||||
|
||||
// BaseLimit is a mixin type for basic resource limits.
|
||||
type BaseLimit struct {
|
||||
Streams int `json:",omitempty"`
|
||||
StreamsInbound int `json:",omitempty"`
|
||||
StreamsOutbound int `json:",omitempty"`
|
||||
Conns int `json:",omitempty"`
|
||||
ConnsInbound int `json:",omitempty"`
|
||||
ConnsOutbound int `json:",omitempty"`
|
||||
FD int `json:",omitempty"`
|
||||
Memory int64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
func valueOrBlockAll(n int) LimitVal {
|
||||
if n == 0 {
|
||||
return BlockAllLimit
|
||||
} else if n == math.MaxInt {
|
||||
return Unlimited
|
||||
}
|
||||
return LimitVal(n)
|
||||
}
|
||||
func valueOrBlockAll64(n int64) LimitVal64 {
|
||||
if n == 0 {
|
||||
return BlockAllLimit64
|
||||
} else if n == math.MaxInt {
|
||||
return Unlimited64
|
||||
}
|
||||
return LimitVal64(n)
|
||||
}
|
||||
|
||||
// ToResourceLimits converts the BaseLimit to a ResourceLimits
|
||||
func (l BaseLimit) ToResourceLimits() ResourceLimits {
|
||||
return ResourceLimits{
|
||||
Streams: valueOrBlockAll(l.Streams),
|
||||
StreamsInbound: valueOrBlockAll(l.StreamsInbound),
|
||||
StreamsOutbound: valueOrBlockAll(l.StreamsOutbound),
|
||||
Conns: valueOrBlockAll(l.Conns),
|
||||
ConnsInbound: valueOrBlockAll(l.ConnsInbound),
|
||||
ConnsOutbound: valueOrBlockAll(l.ConnsOutbound),
|
||||
FD: valueOrBlockAll(l.FD),
|
||||
Memory: valueOrBlockAll64(l.Memory),
|
||||
}
|
||||
}
|
||||
|
||||
// Apply overwrites all zero-valued limits with the values of l2
|
||||
// Must not use a pointer receiver.
|
||||
func (l *BaseLimit) Apply(l2 BaseLimit) {
|
||||
if l.Streams == 0 {
|
||||
l.Streams = l2.Streams
|
||||
}
|
||||
if l.StreamsInbound == 0 {
|
||||
l.StreamsInbound = l2.StreamsInbound
|
||||
}
|
||||
if l.StreamsOutbound == 0 {
|
||||
l.StreamsOutbound = l2.StreamsOutbound
|
||||
}
|
||||
if l.Conns == 0 {
|
||||
l.Conns = l2.Conns
|
||||
}
|
||||
if l.ConnsInbound == 0 {
|
||||
l.ConnsInbound = l2.ConnsInbound
|
||||
}
|
||||
if l.ConnsOutbound == 0 {
|
||||
l.ConnsOutbound = l2.ConnsOutbound
|
||||
}
|
||||
if l.Memory == 0 {
|
||||
l.Memory = l2.Memory
|
||||
}
|
||||
if l.FD == 0 {
|
||||
l.FD = l2.FD
|
||||
}
|
||||
}
|
||||
|
||||
// BaseLimitIncrease is the increase per GiB of allowed memory.
|
||||
type BaseLimitIncrease struct {
|
||||
Streams int `json:",omitempty"`
|
||||
StreamsInbound int `json:",omitempty"`
|
||||
StreamsOutbound int `json:",omitempty"`
|
||||
Conns int `json:",omitempty"`
|
||||
ConnsInbound int `json:",omitempty"`
|
||||
ConnsOutbound int `json:",omitempty"`
|
||||
// Memory is in bytes. Values over 1>>30 (1GiB) don't make sense.
|
||||
Memory int64 `json:",omitempty"`
|
||||
// FDFraction is expected to be >= 0 and <= 1.
|
||||
FDFraction float64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Apply overwrites all zero-valued limits with the values of l2
|
||||
// Must not use a pointer receiver.
|
||||
func (l *BaseLimitIncrease) Apply(l2 BaseLimitIncrease) {
|
||||
if l.Streams == 0 {
|
||||
l.Streams = l2.Streams
|
||||
}
|
||||
if l.StreamsInbound == 0 {
|
||||
l.StreamsInbound = l2.StreamsInbound
|
||||
}
|
||||
if l.StreamsOutbound == 0 {
|
||||
l.StreamsOutbound = l2.StreamsOutbound
|
||||
}
|
||||
if l.Conns == 0 {
|
||||
l.Conns = l2.Conns
|
||||
}
|
||||
if l.ConnsInbound == 0 {
|
||||
l.ConnsInbound = l2.ConnsInbound
|
||||
}
|
||||
if l.ConnsOutbound == 0 {
|
||||
l.ConnsOutbound = l2.ConnsOutbound
|
||||
}
|
||||
if l.Memory == 0 {
|
||||
l.Memory = l2.Memory
|
||||
}
|
||||
if l.FDFraction == 0 {
|
||||
l.FDFraction = l2.FDFraction
|
||||
}
|
||||
}
|
||||
|
||||
func (l BaseLimit) GetStreamLimit(dir network.Direction) int {
|
||||
if dir == network.DirInbound {
|
||||
return l.StreamsInbound
|
||||
} else {
|
||||
return l.StreamsOutbound
|
||||
}
|
||||
}
|
||||
|
||||
func (l BaseLimit) GetStreamTotalLimit() int {
|
||||
return l.Streams
|
||||
}
|
||||
|
||||
func (l BaseLimit) GetConnLimit(dir network.Direction) int {
|
||||
if dir == network.DirInbound {
|
||||
return l.ConnsInbound
|
||||
} else {
|
||||
return l.ConnsOutbound
|
||||
}
|
||||
}
|
||||
|
||||
func (l BaseLimit) GetConnTotalLimit() int {
|
||||
return l.Conns
|
||||
}
|
||||
|
||||
func (l BaseLimit) GetFDLimit() int {
|
||||
return l.FD
|
||||
}
|
||||
|
||||
func (l BaseLimit) GetMemoryLimit() int64 {
|
||||
return l.Memory
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetSystemLimits() Limit {
|
||||
return &l.system
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetTransientLimits() Limit {
|
||||
return &l.transient
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetAllowlistedSystemLimits() Limit {
|
||||
return &l.allowlistedSystem
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetAllowlistedTransientLimits() Limit {
|
||||
return &l.allowlistedTransient
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetServiceLimits(svc string) Limit {
|
||||
sl, ok := l.service[svc]
|
||||
if !ok {
|
||||
return &l.serviceDefault
|
||||
}
|
||||
return &sl
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetServicePeerLimits(svc string) Limit {
|
||||
pl, ok := l.servicePeer[svc]
|
||||
if !ok {
|
||||
return &l.servicePeerDefault
|
||||
}
|
||||
return &pl
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetProtocolLimits(proto protocol.ID) Limit {
|
||||
pl, ok := l.protocol[proto]
|
||||
if !ok {
|
||||
return &l.protocolDefault
|
||||
}
|
||||
return &pl
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetProtocolPeerLimits(proto protocol.ID) Limit {
|
||||
pl, ok := l.protocolPeer[proto]
|
||||
if !ok {
|
||||
return &l.protocolPeerDefault
|
||||
}
|
||||
return &pl
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetPeerLimits(p peer.ID) Limit {
|
||||
pl, ok := l.peer[p]
|
||||
if !ok {
|
||||
return &l.peerDefault
|
||||
}
|
||||
return &pl
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetStreamLimits(_ peer.ID) Limit {
|
||||
return &l.stream
|
||||
}
|
||||
|
||||
func (l *fixedLimiter) GetConnLimits() Limit {
|
||||
return &l.conn
|
||||
}
|
||||
45
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_config_test.backwards-compat.json
generated
vendored
Normal file
45
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_config_test.backwards-compat.json
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"System": {
|
||||
"Memory": 65536,
|
||||
"Conns": 16,
|
||||
"ConnsInbound": 8,
|
||||
"ConnsOutbound": 16,
|
||||
"FD": 16
|
||||
},
|
||||
"ServiceDefault": {
|
||||
"Memory": 8765
|
||||
},
|
||||
"Service": {
|
||||
"A": {
|
||||
"Memory": 8192
|
||||
},
|
||||
"B": {}
|
||||
},
|
||||
"ServicePeerDefault": {
|
||||
"Memory": 2048
|
||||
},
|
||||
"ServicePeer": {
|
||||
"A": {
|
||||
"Memory": 4096
|
||||
}
|
||||
},
|
||||
"ProtocolDefault": {
|
||||
"Memory": 2048
|
||||
},
|
||||
"ProtocolPeerDefault": {
|
||||
"Memory": 1024
|
||||
},
|
||||
"Protocol": {
|
||||
"/A": {
|
||||
"Memory": 8192
|
||||
}
|
||||
},
|
||||
"PeerDefault": {
|
||||
"Memory": 4096
|
||||
},
|
||||
"Peer": {
|
||||
"12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS": {
|
||||
"Memory": 4097
|
||||
}
|
||||
}
|
||||
}
|
||||
45
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_config_test.json
generated
vendored
Normal file
45
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_config_test.json
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"System": {
|
||||
"Memory": 65536,
|
||||
"Conns": 16,
|
||||
"ConnsInbound": 8,
|
||||
"ConnsOutbound": 16,
|
||||
"FD": 16
|
||||
},
|
||||
"ServiceDefault": {
|
||||
"Memory": 8765
|
||||
},
|
||||
"Service": {
|
||||
"A": {
|
||||
"Memory": 8192
|
||||
},
|
||||
"B": {}
|
||||
},
|
||||
"ServicePeerDefault": {
|
||||
"Memory": 2048
|
||||
},
|
||||
"ServicePeer": {
|
||||
"A": {
|
||||
"Memory": 4096
|
||||
}
|
||||
},
|
||||
"ProtocolDefault": {
|
||||
"Memory": 2048
|
||||
},
|
||||
"ProtocolPeerDefault": {
|
||||
"Memory": 1024
|
||||
},
|
||||
"Protocol": {
|
||||
"/A": {
|
||||
"Memory": 8192
|
||||
}
|
||||
},
|
||||
"PeerDefault": {
|
||||
"Memory": 4096
|
||||
},
|
||||
"Peer": {
|
||||
"12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS": {
|
||||
"Memory": 4097
|
||||
}
|
||||
}
|
||||
}
|
||||
112
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_config_test_default.json
generated
vendored
Normal file
112
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_config_test_default.json
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"System": {
|
||||
"Streams": 18432,
|
||||
"StreamsInbound": 9216,
|
||||
"StreamsOutbound": 18432,
|
||||
"Conns": 1152,
|
||||
"ConnsInbound": 576,
|
||||
"ConnsOutbound": 1152,
|
||||
"FD": 16384,
|
||||
"Memory": "8724152320"
|
||||
},
|
||||
"Transient": {
|
||||
"Streams": 2304,
|
||||
"StreamsInbound": 1152,
|
||||
"StreamsOutbound": 2304,
|
||||
"Conns": 320,
|
||||
"ConnsInbound": 160,
|
||||
"ConnsOutbound": 320,
|
||||
"FD": 4096,
|
||||
"Memory": "1107296256"
|
||||
},
|
||||
"AllowlistedSystem": {
|
||||
"Streams": 18432,
|
||||
"StreamsInbound": 9216,
|
||||
"StreamsOutbound": 18432,
|
||||
"Conns": 1152,
|
||||
"ConnsInbound": 576,
|
||||
"ConnsOutbound": 1152,
|
||||
"FD": 16384,
|
||||
"Memory": "8724152320"
|
||||
},
|
||||
"AllowlistedTransient": {
|
||||
"Streams": 2304,
|
||||
"StreamsInbound": 1152,
|
||||
"StreamsOutbound": 2304,
|
||||
"Conns": 320,
|
||||
"ConnsInbound": 160,
|
||||
"ConnsOutbound": 320,
|
||||
"FD": 4096,
|
||||
"Memory": "1107296256"
|
||||
},
|
||||
"ServiceDefault": {
|
||||
"Streams": 20480,
|
||||
"StreamsInbound": 5120,
|
||||
"StreamsOutbound": 20480,
|
||||
"Conns": "blockAll",
|
||||
"ConnsInbound": "blockAll",
|
||||
"ConnsOutbound": "blockAll",
|
||||
"FD": "blockAll",
|
||||
"Memory": "1140850688"
|
||||
},
|
||||
"ServicePeerDefault": {
|
||||
"Streams": 320,
|
||||
"StreamsInbound": 160,
|
||||
"StreamsOutbound": 320,
|
||||
"Conns": "blockAll",
|
||||
"ConnsInbound": "blockAll",
|
||||
"ConnsOutbound": "blockAll",
|
||||
"FD": "blockAll",
|
||||
"Memory": "50331648"
|
||||
},
|
||||
"ProtocolDefault": {
|
||||
"Streams": 6144,
|
||||
"StreamsInbound": 2560,
|
||||
"StreamsOutbound": 6144,
|
||||
"Conns": "blockAll",
|
||||
"ConnsInbound": "blockAll",
|
||||
"ConnsOutbound": "blockAll",
|
||||
"FD": "blockAll",
|
||||
"Memory": "1442840576"
|
||||
},
|
||||
"ProtocolPeerDefault": {
|
||||
"Streams": 384,
|
||||
"StreamsInbound": 96,
|
||||
"StreamsOutbound": 192,
|
||||
"Conns": "blockAll",
|
||||
"ConnsInbound": "blockAll",
|
||||
"ConnsOutbound": "blockAll",
|
||||
"FD": "blockAll",
|
||||
"Memory": "16777248"
|
||||
},
|
||||
"PeerDefault": {
|
||||
"Streams": 2560,
|
||||
"StreamsInbound": 1280,
|
||||
"StreamsOutbound": 2560,
|
||||
"Conns": 8,
|
||||
"ConnsInbound": 8,
|
||||
"ConnsOutbound": 8,
|
||||
"FD": 256,
|
||||
"Memory": "1140850688"
|
||||
},
|
||||
"Conn": {
|
||||
"Streams": "blockAll",
|
||||
"StreamsInbound": "blockAll",
|
||||
"StreamsOutbound": "blockAll",
|
||||
"Conns": 1,
|
||||
"ConnsInbound": 1,
|
||||
"ConnsOutbound": 1,
|
||||
"FD": 1,
|
||||
"Memory": "33554432"
|
||||
},
|
||||
"Stream": {
|
||||
"Streams": 1,
|
||||
"StreamsInbound": 1,
|
||||
"StreamsOutbound": 1,
|
||||
"Conns": "blockAll",
|
||||
"ConnsInbound": "blockAll",
|
||||
"ConnsOutbound": "blockAll",
|
||||
"FD": "blockAll",
|
||||
"Memory": "16777216"
|
||||
}
|
||||
}
|
||||
879
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_defaults.go
generated
vendored
Normal file
879
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/limit_defaults.go
generated
vendored
Normal file
@@ -0,0 +1,879 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
|
||||
"github.com/pbnjay/memory"
|
||||
)
|
||||
|
||||
type baseLimitConfig struct {
|
||||
BaseLimit BaseLimit
|
||||
BaseLimitIncrease BaseLimitIncrease
|
||||
}
|
||||
|
||||
// ScalingLimitConfig is a struct for configuring default limits.
|
||||
// {}BaseLimit is the limits that Apply for a minimal node (128 MB of memory for libp2p) and 256 file descriptors.
|
||||
// {}LimitIncrease is the additional limit granted for every additional 1 GB of RAM.
|
||||
type ScalingLimitConfig struct {
|
||||
SystemBaseLimit BaseLimit
|
||||
SystemLimitIncrease BaseLimitIncrease
|
||||
|
||||
TransientBaseLimit BaseLimit
|
||||
TransientLimitIncrease BaseLimitIncrease
|
||||
|
||||
AllowlistedSystemBaseLimit BaseLimit
|
||||
AllowlistedSystemLimitIncrease BaseLimitIncrease
|
||||
|
||||
AllowlistedTransientBaseLimit BaseLimit
|
||||
AllowlistedTransientLimitIncrease BaseLimitIncrease
|
||||
|
||||
ServiceBaseLimit BaseLimit
|
||||
ServiceLimitIncrease BaseLimitIncrease
|
||||
ServiceLimits map[string]baseLimitConfig // use AddServiceLimit to modify
|
||||
|
||||
ServicePeerBaseLimit BaseLimit
|
||||
ServicePeerLimitIncrease BaseLimitIncrease
|
||||
ServicePeerLimits map[string]baseLimitConfig // use AddServicePeerLimit to modify
|
||||
|
||||
ProtocolBaseLimit BaseLimit
|
||||
ProtocolLimitIncrease BaseLimitIncrease
|
||||
ProtocolLimits map[protocol.ID]baseLimitConfig // use AddProtocolLimit to modify
|
||||
|
||||
ProtocolPeerBaseLimit BaseLimit
|
||||
ProtocolPeerLimitIncrease BaseLimitIncrease
|
||||
ProtocolPeerLimits map[protocol.ID]baseLimitConfig // use AddProtocolPeerLimit to modify
|
||||
|
||||
PeerBaseLimit BaseLimit
|
||||
PeerLimitIncrease BaseLimitIncrease
|
||||
PeerLimits map[peer.ID]baseLimitConfig // use AddPeerLimit to modify
|
||||
|
||||
ConnBaseLimit BaseLimit
|
||||
ConnLimitIncrease BaseLimitIncrease
|
||||
|
||||
StreamBaseLimit BaseLimit
|
||||
StreamLimitIncrease BaseLimitIncrease
|
||||
}
|
||||
|
||||
func (cfg *ScalingLimitConfig) AddServiceLimit(svc string, base BaseLimit, inc BaseLimitIncrease) {
|
||||
if cfg.ServiceLimits == nil {
|
||||
cfg.ServiceLimits = make(map[string]baseLimitConfig)
|
||||
}
|
||||
cfg.ServiceLimits[svc] = baseLimitConfig{
|
||||
BaseLimit: base,
|
||||
BaseLimitIncrease: inc,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ScalingLimitConfig) AddProtocolLimit(proto protocol.ID, base BaseLimit, inc BaseLimitIncrease) {
|
||||
if cfg.ProtocolLimits == nil {
|
||||
cfg.ProtocolLimits = make(map[protocol.ID]baseLimitConfig)
|
||||
}
|
||||
cfg.ProtocolLimits[proto] = baseLimitConfig{
|
||||
BaseLimit: base,
|
||||
BaseLimitIncrease: inc,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ScalingLimitConfig) AddPeerLimit(p peer.ID, base BaseLimit, inc BaseLimitIncrease) {
|
||||
if cfg.PeerLimits == nil {
|
||||
cfg.PeerLimits = make(map[peer.ID]baseLimitConfig)
|
||||
}
|
||||
cfg.PeerLimits[p] = baseLimitConfig{
|
||||
BaseLimit: base,
|
||||
BaseLimitIncrease: inc,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ScalingLimitConfig) AddServicePeerLimit(svc string, base BaseLimit, inc BaseLimitIncrease) {
|
||||
if cfg.ServicePeerLimits == nil {
|
||||
cfg.ServicePeerLimits = make(map[string]baseLimitConfig)
|
||||
}
|
||||
cfg.ServicePeerLimits[svc] = baseLimitConfig{
|
||||
BaseLimit: base,
|
||||
BaseLimitIncrease: inc,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ScalingLimitConfig) AddProtocolPeerLimit(proto protocol.ID, base BaseLimit, inc BaseLimitIncrease) {
|
||||
if cfg.ProtocolPeerLimits == nil {
|
||||
cfg.ProtocolPeerLimits = make(map[protocol.ID]baseLimitConfig)
|
||||
}
|
||||
cfg.ProtocolPeerLimits[proto] = baseLimitConfig{
|
||||
BaseLimit: base,
|
||||
BaseLimitIncrease: inc,
|
||||
}
|
||||
}
|
||||
|
||||
type LimitVal int
|
||||
|
||||
const (
|
||||
// DefaultLimit is the default value for resources. The exact value depends on the context, but will get values from `DefaultLimits`.
|
||||
DefaultLimit LimitVal = 0
|
||||
// Unlimited is the value for unlimited resources. An arbitrarily high number will also work.
|
||||
Unlimited LimitVal = -1
|
||||
// BlockAllLimit is the LimitVal for allowing no amount of resources.
|
||||
BlockAllLimit LimitVal = -2
|
||||
)
|
||||
|
||||
func (l LimitVal) MarshalJSON() ([]byte, error) {
|
||||
if l == Unlimited {
|
||||
return json.Marshal("unlimited")
|
||||
} else if l == DefaultLimit {
|
||||
return json.Marshal("default")
|
||||
} else if l == BlockAllLimit {
|
||||
return json.Marshal("blockAll")
|
||||
}
|
||||
return json.Marshal(int(l))
|
||||
}
|
||||
|
||||
func (l *LimitVal) UnmarshalJSON(b []byte) error {
|
||||
if string(b) == `"default"` {
|
||||
*l = DefaultLimit
|
||||
return nil
|
||||
} else if string(b) == `"unlimited"` {
|
||||
*l = Unlimited
|
||||
return nil
|
||||
} else if string(b) == `"blockAll"` {
|
||||
*l = BlockAllLimit
|
||||
return nil
|
||||
}
|
||||
|
||||
var val int
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if val == 0 {
|
||||
// If there is an explicit 0 in the JSON we should interpret this as block all.
|
||||
*l = BlockAllLimit
|
||||
return nil
|
||||
}
|
||||
|
||||
*l = LimitVal(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l LimitVal) Build(defaultVal int) int {
|
||||
if l == DefaultLimit {
|
||||
return defaultVal
|
||||
}
|
||||
if l == Unlimited {
|
||||
return math.MaxInt
|
||||
}
|
||||
if l == BlockAllLimit {
|
||||
return 0
|
||||
}
|
||||
return int(l)
|
||||
}
|
||||
|
||||
type LimitVal64 int64
|
||||
|
||||
const (
|
||||
// Default is the default value for resources.
|
||||
DefaultLimit64 LimitVal64 = 0
|
||||
// Unlimited is the value for unlimited resources.
|
||||
Unlimited64 LimitVal64 = -1
|
||||
// BlockAllLimit64 is the LimitVal for allowing no amount of resources.
|
||||
BlockAllLimit64 LimitVal64 = -2
|
||||
)
|
||||
|
||||
func (l LimitVal64) MarshalJSON() ([]byte, error) {
|
||||
if l == Unlimited64 {
|
||||
return json.Marshal("unlimited")
|
||||
} else if l == DefaultLimit64 {
|
||||
return json.Marshal("default")
|
||||
} else if l == BlockAllLimit64 {
|
||||
return json.Marshal("blockAll")
|
||||
}
|
||||
|
||||
// Convert this to a string because JSON doesn't support 64-bit integers.
|
||||
return json.Marshal(strconv.FormatInt(int64(l), 10))
|
||||
}
|
||||
|
||||
func (l *LimitVal64) UnmarshalJSON(b []byte) error {
|
||||
if string(b) == `"default"` {
|
||||
*l = DefaultLimit64
|
||||
return nil
|
||||
} else if string(b) == `"unlimited"` {
|
||||
*l = Unlimited64
|
||||
return nil
|
||||
} else if string(b) == `"blockAll"` {
|
||||
*l = BlockAllLimit64
|
||||
return nil
|
||||
}
|
||||
|
||||
var val string
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
// Is this an integer? Possible because of backwards compatibility.
|
||||
var val int
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal limit value: %w", err)
|
||||
}
|
||||
|
||||
if val == 0 {
|
||||
// If there is an explicit 0 in the JSON we should interpret this as block all.
|
||||
*l = BlockAllLimit64
|
||||
return nil
|
||||
}
|
||||
|
||||
*l = LimitVal64(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
i, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
// If there is an explicit 0 in the JSON we should interpret this as block all.
|
||||
*l = BlockAllLimit64
|
||||
return nil
|
||||
}
|
||||
|
||||
*l = LimitVal64(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l LimitVal64) Build(defaultVal int64) int64 {
|
||||
if l == DefaultLimit64 {
|
||||
return defaultVal
|
||||
}
|
||||
if l == Unlimited64 {
|
||||
return math.MaxInt64
|
||||
}
|
||||
if l == BlockAllLimit64 {
|
||||
return 0
|
||||
}
|
||||
return int64(l)
|
||||
}
|
||||
|
||||
// ResourceLimits is the type for basic resource limits.
|
||||
type ResourceLimits struct {
|
||||
Streams LimitVal `json:",omitempty"`
|
||||
StreamsInbound LimitVal `json:",omitempty"`
|
||||
StreamsOutbound LimitVal `json:",omitempty"`
|
||||
Conns LimitVal `json:",omitempty"`
|
||||
ConnsInbound LimitVal `json:",omitempty"`
|
||||
ConnsOutbound LimitVal `json:",omitempty"`
|
||||
FD LimitVal `json:",omitempty"`
|
||||
Memory LimitVal64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (l *ResourceLimits) IsDefault() bool {
|
||||
if l == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if l.Streams == DefaultLimit &&
|
||||
l.StreamsInbound == DefaultLimit &&
|
||||
l.StreamsOutbound == DefaultLimit &&
|
||||
l.Conns == DefaultLimit &&
|
||||
l.ConnsInbound == DefaultLimit &&
|
||||
l.ConnsOutbound == DefaultLimit &&
|
||||
l.FD == DefaultLimit &&
|
||||
l.Memory == DefaultLimit64 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *ResourceLimits) ToMaybeNilPtr() *ResourceLimits {
|
||||
if l.IsDefault() {
|
||||
return nil
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Apply overwrites all default limits with the values of l2
|
||||
func (l *ResourceLimits) Apply(l2 ResourceLimits) {
|
||||
if l.Streams == DefaultLimit {
|
||||
l.Streams = l2.Streams
|
||||
}
|
||||
if l.StreamsInbound == DefaultLimit {
|
||||
l.StreamsInbound = l2.StreamsInbound
|
||||
}
|
||||
if l.StreamsOutbound == DefaultLimit {
|
||||
l.StreamsOutbound = l2.StreamsOutbound
|
||||
}
|
||||
if l.Conns == DefaultLimit {
|
||||
l.Conns = l2.Conns
|
||||
}
|
||||
if l.ConnsInbound == DefaultLimit {
|
||||
l.ConnsInbound = l2.ConnsInbound
|
||||
}
|
||||
if l.ConnsOutbound == DefaultLimit {
|
||||
l.ConnsOutbound = l2.ConnsOutbound
|
||||
}
|
||||
if l.FD == DefaultLimit {
|
||||
l.FD = l2.FD
|
||||
}
|
||||
if l.Memory == DefaultLimit64 {
|
||||
l.Memory = l2.Memory
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ResourceLimits) Build(defaults Limit) BaseLimit {
|
||||
if l == nil {
|
||||
return BaseLimit{
|
||||
Streams: defaults.GetStreamTotalLimit(),
|
||||
StreamsInbound: defaults.GetStreamLimit(network.DirInbound),
|
||||
StreamsOutbound: defaults.GetStreamLimit(network.DirOutbound),
|
||||
Conns: defaults.GetConnTotalLimit(),
|
||||
ConnsInbound: defaults.GetConnLimit(network.DirInbound),
|
||||
ConnsOutbound: defaults.GetConnLimit(network.DirOutbound),
|
||||
FD: defaults.GetFDLimit(),
|
||||
Memory: defaults.GetMemoryLimit(),
|
||||
}
|
||||
}
|
||||
|
||||
return BaseLimit{
|
||||
Streams: l.Streams.Build(defaults.GetStreamTotalLimit()),
|
||||
StreamsInbound: l.StreamsInbound.Build(defaults.GetStreamLimit(network.DirInbound)),
|
||||
StreamsOutbound: l.StreamsOutbound.Build(defaults.GetStreamLimit(network.DirOutbound)),
|
||||
Conns: l.Conns.Build(defaults.GetConnTotalLimit()),
|
||||
ConnsInbound: l.ConnsInbound.Build(defaults.GetConnLimit(network.DirInbound)),
|
||||
ConnsOutbound: l.ConnsOutbound.Build(defaults.GetConnLimit(network.DirOutbound)),
|
||||
FD: l.FD.Build(defaults.GetFDLimit()),
|
||||
Memory: l.Memory.Build(defaults.GetMemoryLimit()),
|
||||
}
|
||||
}
|
||||
|
||||
type PartialLimitConfig struct {
|
||||
System ResourceLimits `json:",omitempty"`
|
||||
Transient ResourceLimits `json:",omitempty"`
|
||||
|
||||
// Limits that are applied to resources with an allowlisted multiaddr.
|
||||
// These will only be used if the normal System & Transient limits are
|
||||
// reached.
|
||||
AllowlistedSystem ResourceLimits `json:",omitempty"`
|
||||
AllowlistedTransient ResourceLimits `json:",omitempty"`
|
||||
|
||||
ServiceDefault ResourceLimits `json:",omitempty"`
|
||||
Service map[string]ResourceLimits `json:",omitempty"`
|
||||
|
||||
ServicePeerDefault ResourceLimits `json:",omitempty"`
|
||||
ServicePeer map[string]ResourceLimits `json:",omitempty"`
|
||||
|
||||
ProtocolDefault ResourceLimits `json:",omitempty"`
|
||||
Protocol map[protocol.ID]ResourceLimits `json:",omitempty"`
|
||||
|
||||
ProtocolPeerDefault ResourceLimits `json:",omitempty"`
|
||||
ProtocolPeer map[protocol.ID]ResourceLimits `json:",omitempty"`
|
||||
|
||||
PeerDefault ResourceLimits `json:",omitempty"`
|
||||
Peer map[peer.ID]ResourceLimits `json:",omitempty"`
|
||||
|
||||
Conn ResourceLimits `json:",omitempty"`
|
||||
Stream ResourceLimits `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (cfg *PartialLimitConfig) MarshalJSON() ([]byte, error) {
|
||||
// we want to marshal the encoded peer id
|
||||
encodedPeerMap := make(map[string]ResourceLimits, len(cfg.Peer))
|
||||
for p, v := range cfg.Peer {
|
||||
encodedPeerMap[p.String()] = v
|
||||
}
|
||||
|
||||
type Alias PartialLimitConfig
|
||||
return json.Marshal(&struct {
|
||||
*Alias
|
||||
// String so we can have the properly marshalled peer id
|
||||
Peer map[string]ResourceLimits `json:",omitempty"`
|
||||
|
||||
// The rest of the fields as pointers so that we omit empty values in the serialized result
|
||||
System *ResourceLimits `json:",omitempty"`
|
||||
Transient *ResourceLimits `json:",omitempty"`
|
||||
AllowlistedSystem *ResourceLimits `json:",omitempty"`
|
||||
AllowlistedTransient *ResourceLimits `json:",omitempty"`
|
||||
|
||||
ServiceDefault *ResourceLimits `json:",omitempty"`
|
||||
|
||||
ServicePeerDefault *ResourceLimits `json:",omitempty"`
|
||||
|
||||
ProtocolDefault *ResourceLimits `json:",omitempty"`
|
||||
|
||||
ProtocolPeerDefault *ResourceLimits `json:",omitempty"`
|
||||
|
||||
PeerDefault *ResourceLimits `json:",omitempty"`
|
||||
|
||||
Conn *ResourceLimits `json:",omitempty"`
|
||||
Stream *ResourceLimits `json:",omitempty"`
|
||||
}{
|
||||
Alias: (*Alias)(cfg),
|
||||
Peer: encodedPeerMap,
|
||||
|
||||
System: cfg.System.ToMaybeNilPtr(),
|
||||
Transient: cfg.Transient.ToMaybeNilPtr(),
|
||||
AllowlistedSystem: cfg.AllowlistedSystem.ToMaybeNilPtr(),
|
||||
AllowlistedTransient: cfg.AllowlistedTransient.ToMaybeNilPtr(),
|
||||
ServiceDefault: cfg.ServiceDefault.ToMaybeNilPtr(),
|
||||
ServicePeerDefault: cfg.ServicePeerDefault.ToMaybeNilPtr(),
|
||||
ProtocolDefault: cfg.ProtocolDefault.ToMaybeNilPtr(),
|
||||
ProtocolPeerDefault: cfg.ProtocolPeerDefault.ToMaybeNilPtr(),
|
||||
PeerDefault: cfg.PeerDefault.ToMaybeNilPtr(),
|
||||
Conn: cfg.Conn.ToMaybeNilPtr(),
|
||||
Stream: cfg.Stream.ToMaybeNilPtr(),
|
||||
})
|
||||
}
|
||||
|
||||
func applyResourceLimitsMap[K comparable](this *map[K]ResourceLimits, other map[K]ResourceLimits, fallbackDefault ResourceLimits) {
|
||||
for k, l := range *this {
|
||||
r := fallbackDefault
|
||||
if l2, ok := other[k]; ok {
|
||||
r = l2
|
||||
}
|
||||
l.Apply(r)
|
||||
(*this)[k] = l
|
||||
}
|
||||
if *this == nil && other != nil {
|
||||
*this = make(map[K]ResourceLimits)
|
||||
}
|
||||
for k, l := range other {
|
||||
if _, ok := (*this)[k]; !ok {
|
||||
(*this)[k] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *PartialLimitConfig) Apply(c PartialLimitConfig) {
|
||||
cfg.System.Apply(c.System)
|
||||
cfg.Transient.Apply(c.Transient)
|
||||
cfg.AllowlistedSystem.Apply(c.AllowlistedSystem)
|
||||
cfg.AllowlistedTransient.Apply(c.AllowlistedTransient)
|
||||
cfg.ServiceDefault.Apply(c.ServiceDefault)
|
||||
cfg.ServicePeerDefault.Apply(c.ServicePeerDefault)
|
||||
cfg.ProtocolDefault.Apply(c.ProtocolDefault)
|
||||
cfg.ProtocolPeerDefault.Apply(c.ProtocolPeerDefault)
|
||||
cfg.PeerDefault.Apply(c.PeerDefault)
|
||||
cfg.Conn.Apply(c.Conn)
|
||||
cfg.Stream.Apply(c.Stream)
|
||||
|
||||
applyResourceLimitsMap(&cfg.Service, c.Service, cfg.ServiceDefault)
|
||||
applyResourceLimitsMap(&cfg.ServicePeer, c.ServicePeer, cfg.ServicePeerDefault)
|
||||
applyResourceLimitsMap(&cfg.Protocol, c.Protocol, cfg.ProtocolDefault)
|
||||
applyResourceLimitsMap(&cfg.ProtocolPeer, c.ProtocolPeer, cfg.ProtocolPeerDefault)
|
||||
applyResourceLimitsMap(&cfg.Peer, c.Peer, cfg.PeerDefault)
|
||||
}
|
||||
|
||||
func (cfg PartialLimitConfig) Build(defaults ConcreteLimitConfig) ConcreteLimitConfig {
|
||||
out := defaults
|
||||
|
||||
out.system = cfg.System.Build(defaults.system)
|
||||
out.transient = cfg.Transient.Build(defaults.transient)
|
||||
out.allowlistedSystem = cfg.AllowlistedSystem.Build(defaults.allowlistedSystem)
|
||||
out.allowlistedTransient = cfg.AllowlistedTransient.Build(defaults.allowlistedTransient)
|
||||
out.serviceDefault = cfg.ServiceDefault.Build(defaults.serviceDefault)
|
||||
out.servicePeerDefault = cfg.ServicePeerDefault.Build(defaults.servicePeerDefault)
|
||||
out.protocolDefault = cfg.ProtocolDefault.Build(defaults.protocolDefault)
|
||||
out.protocolPeerDefault = cfg.ProtocolPeerDefault.Build(defaults.protocolPeerDefault)
|
||||
out.peerDefault = cfg.PeerDefault.Build(defaults.peerDefault)
|
||||
out.conn = cfg.Conn.Build(defaults.conn)
|
||||
out.stream = cfg.Stream.Build(defaults.stream)
|
||||
|
||||
out.service = buildMapWithDefault(cfg.Service, defaults.service, out.serviceDefault)
|
||||
out.servicePeer = buildMapWithDefault(cfg.ServicePeer, defaults.servicePeer, out.servicePeerDefault)
|
||||
out.protocol = buildMapWithDefault(cfg.Protocol, defaults.protocol, out.protocolDefault)
|
||||
out.protocolPeer = buildMapWithDefault(cfg.ProtocolPeer, defaults.protocolPeer, out.protocolPeerDefault)
|
||||
out.peer = buildMapWithDefault(cfg.Peer, defaults.peer, out.peerDefault)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func buildMapWithDefault[K comparable](definedLimits map[K]ResourceLimits, defaults map[K]BaseLimit, fallbackDefault BaseLimit) map[K]BaseLimit {
|
||||
if definedLimits == nil && defaults == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make(map[K]BaseLimit)
|
||||
for k, l := range defaults {
|
||||
out[k] = l
|
||||
}
|
||||
|
||||
for k, l := range definedLimits {
|
||||
if defaultForKey, ok := out[k]; ok {
|
||||
out[k] = l.Build(defaultForKey)
|
||||
} else {
|
||||
out[k] = l.Build(fallbackDefault)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ConcreteLimitConfig is similar to PartialLimitConfig, but all values are defined.
|
||||
// There is no unset "default" value. Commonly constructed by calling
|
||||
// PartialLimitConfig.Build(rcmgr.DefaultLimits.AutoScale())
|
||||
type ConcreteLimitConfig struct {
|
||||
system BaseLimit
|
||||
transient BaseLimit
|
||||
|
||||
// Limits that are applied to resources with an allowlisted multiaddr.
|
||||
// These will only be used if the normal System & Transient limits are
|
||||
// reached.
|
||||
allowlistedSystem BaseLimit
|
||||
allowlistedTransient BaseLimit
|
||||
|
||||
serviceDefault BaseLimit
|
||||
service map[string]BaseLimit
|
||||
|
||||
servicePeerDefault BaseLimit
|
||||
servicePeer map[string]BaseLimit
|
||||
|
||||
protocolDefault BaseLimit
|
||||
protocol map[protocol.ID]BaseLimit
|
||||
|
||||
protocolPeerDefault BaseLimit
|
||||
protocolPeer map[protocol.ID]BaseLimit
|
||||
|
||||
peerDefault BaseLimit
|
||||
peer map[peer.ID]BaseLimit
|
||||
|
||||
conn BaseLimit
|
||||
stream BaseLimit
|
||||
}
|
||||
|
||||
func resourceLimitsMapFromBaseLimitMap[K comparable](baseLimitMap map[K]BaseLimit) map[K]ResourceLimits {
|
||||
if baseLimitMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make(map[K]ResourceLimits)
|
||||
for k, l := range baseLimitMap {
|
||||
out[k] = l.ToResourceLimits()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ToPartialLimitConfig converts a ConcreteLimitConfig to a PartialLimitConfig.
|
||||
// The returned PartialLimitConfig will have no default values.
|
||||
func (cfg ConcreteLimitConfig) ToPartialLimitConfig() PartialLimitConfig {
|
||||
return PartialLimitConfig{
|
||||
System: cfg.system.ToResourceLimits(),
|
||||
Transient: cfg.transient.ToResourceLimits(),
|
||||
AllowlistedSystem: cfg.allowlistedSystem.ToResourceLimits(),
|
||||
AllowlistedTransient: cfg.allowlistedTransient.ToResourceLimits(),
|
||||
ServiceDefault: cfg.serviceDefault.ToResourceLimits(),
|
||||
Service: resourceLimitsMapFromBaseLimitMap(cfg.service),
|
||||
ServicePeerDefault: cfg.servicePeerDefault.ToResourceLimits(),
|
||||
ServicePeer: resourceLimitsMapFromBaseLimitMap(cfg.servicePeer),
|
||||
ProtocolDefault: cfg.protocolDefault.ToResourceLimits(),
|
||||
Protocol: resourceLimitsMapFromBaseLimitMap(cfg.protocol),
|
||||
ProtocolPeerDefault: cfg.protocolPeerDefault.ToResourceLimits(),
|
||||
ProtocolPeer: resourceLimitsMapFromBaseLimitMap(cfg.protocolPeer),
|
||||
PeerDefault: cfg.peerDefault.ToResourceLimits(),
|
||||
Peer: resourceLimitsMapFromBaseLimitMap(cfg.peer),
|
||||
Conn: cfg.conn.ToResourceLimits(),
|
||||
Stream: cfg.stream.ToResourceLimits(),
|
||||
}
|
||||
}
|
||||
|
||||
// Scale scales up a limit configuration.
|
||||
// memory is the amount of memory that the stack is allowed to consume,
|
||||
// for a dedicated node it's recommended to use 1/8 of the installed system memory.
|
||||
// If memory is smaller than 128 MB, the base configuration will be used.
|
||||
func (cfg *ScalingLimitConfig) Scale(memory int64, numFD int) ConcreteLimitConfig {
|
||||
lc := ConcreteLimitConfig{
|
||||
system: scale(cfg.SystemBaseLimit, cfg.SystemLimitIncrease, memory, numFD),
|
||||
transient: scale(cfg.TransientBaseLimit, cfg.TransientLimitIncrease, memory, numFD),
|
||||
allowlistedSystem: scale(cfg.AllowlistedSystemBaseLimit, cfg.AllowlistedSystemLimitIncrease, memory, numFD),
|
||||
allowlistedTransient: scale(cfg.AllowlistedTransientBaseLimit, cfg.AllowlistedTransientLimitIncrease, memory, numFD),
|
||||
serviceDefault: scale(cfg.ServiceBaseLimit, cfg.ServiceLimitIncrease, memory, numFD),
|
||||
servicePeerDefault: scale(cfg.ServicePeerBaseLimit, cfg.ServicePeerLimitIncrease, memory, numFD),
|
||||
protocolDefault: scale(cfg.ProtocolBaseLimit, cfg.ProtocolLimitIncrease, memory, numFD),
|
||||
protocolPeerDefault: scale(cfg.ProtocolPeerBaseLimit, cfg.ProtocolPeerLimitIncrease, memory, numFD),
|
||||
peerDefault: scale(cfg.PeerBaseLimit, cfg.PeerLimitIncrease, memory, numFD),
|
||||
conn: scale(cfg.ConnBaseLimit, cfg.ConnLimitIncrease, memory, numFD),
|
||||
stream: scale(cfg.StreamBaseLimit, cfg.ConnLimitIncrease, memory, numFD),
|
||||
}
|
||||
if cfg.ServiceLimits != nil {
|
||||
lc.service = make(map[string]BaseLimit)
|
||||
for svc, l := range cfg.ServiceLimits {
|
||||
lc.service[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
|
||||
}
|
||||
}
|
||||
if cfg.ProtocolLimits != nil {
|
||||
lc.protocol = make(map[protocol.ID]BaseLimit)
|
||||
for proto, l := range cfg.ProtocolLimits {
|
||||
lc.protocol[proto] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
|
||||
}
|
||||
}
|
||||
if cfg.PeerLimits != nil {
|
||||
lc.peer = make(map[peer.ID]BaseLimit)
|
||||
for p, l := range cfg.PeerLimits {
|
||||
lc.peer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
|
||||
}
|
||||
}
|
||||
if cfg.ServicePeerLimits != nil {
|
||||
lc.servicePeer = make(map[string]BaseLimit)
|
||||
for svc, l := range cfg.ServicePeerLimits {
|
||||
lc.servicePeer[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
|
||||
}
|
||||
}
|
||||
if cfg.ProtocolPeerLimits != nil {
|
||||
lc.protocolPeer = make(map[protocol.ID]BaseLimit)
|
||||
for p, l := range cfg.ProtocolPeerLimits {
|
||||
lc.protocolPeer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
|
||||
}
|
||||
}
|
||||
return lc
|
||||
}
|
||||
|
||||
func (cfg *ScalingLimitConfig) AutoScale() ConcreteLimitConfig {
|
||||
return cfg.Scale(
|
||||
int64(memory.TotalMemory())/8,
|
||||
getNumFDs()/2,
|
||||
)
|
||||
}
|
||||
|
||||
func scale(base BaseLimit, inc BaseLimitIncrease, memory int64, numFD int) BaseLimit {
|
||||
// mebibytesAvailable represents how many MiBs we're allowed to use. Used to
|
||||
// scale the limits. If this is below 128MiB we set it to 0 to just use the
|
||||
// base amounts.
|
||||
var mebibytesAvailable int
|
||||
if memory > 128<<20 {
|
||||
mebibytesAvailable = int((memory) >> 20)
|
||||
}
|
||||
l := BaseLimit{
|
||||
StreamsInbound: base.StreamsInbound + (inc.StreamsInbound*mebibytesAvailable)>>10,
|
||||
StreamsOutbound: base.StreamsOutbound + (inc.StreamsOutbound*mebibytesAvailable)>>10,
|
||||
Streams: base.Streams + (inc.Streams*mebibytesAvailable)>>10,
|
||||
ConnsInbound: base.ConnsInbound + (inc.ConnsInbound*mebibytesAvailable)>>10,
|
||||
ConnsOutbound: base.ConnsOutbound + (inc.ConnsOutbound*mebibytesAvailable)>>10,
|
||||
Conns: base.Conns + (inc.Conns*mebibytesAvailable)>>10,
|
||||
Memory: base.Memory + (inc.Memory*int64(mebibytesAvailable))>>10,
|
||||
FD: base.FD,
|
||||
}
|
||||
if inc.FDFraction > 0 && numFD > 0 {
|
||||
l.FD = int(inc.FDFraction * float64(numFD))
|
||||
if l.FD < base.FD {
|
||||
// Use at least the base amount
|
||||
l.FD = base.FD
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// DefaultLimits are the limits used by the default limiter constructors.
|
||||
var DefaultLimits = ScalingLimitConfig{
|
||||
SystemBaseLimit: BaseLimit{
|
||||
ConnsInbound: 64,
|
||||
ConnsOutbound: 128,
|
||||
Conns: 128,
|
||||
StreamsInbound: 64 * 16,
|
||||
StreamsOutbound: 128 * 16,
|
||||
Streams: 128 * 16,
|
||||
Memory: 128 << 20,
|
||||
FD: 256,
|
||||
},
|
||||
|
||||
SystemLimitIncrease: BaseLimitIncrease{
|
||||
ConnsInbound: 64,
|
||||
ConnsOutbound: 128,
|
||||
Conns: 128,
|
||||
StreamsInbound: 64 * 16,
|
||||
StreamsOutbound: 128 * 16,
|
||||
Streams: 128 * 16,
|
||||
Memory: 1 << 30,
|
||||
FDFraction: 1,
|
||||
},
|
||||
|
||||
TransientBaseLimit: BaseLimit{
|
||||
ConnsInbound: 32,
|
||||
ConnsOutbound: 64,
|
||||
Conns: 64,
|
||||
StreamsInbound: 128,
|
||||
StreamsOutbound: 256,
|
||||
Streams: 256,
|
||||
Memory: 32 << 20,
|
||||
FD: 64,
|
||||
},
|
||||
|
||||
TransientLimitIncrease: BaseLimitIncrease{
|
||||
ConnsInbound: 16,
|
||||
ConnsOutbound: 32,
|
||||
Conns: 32,
|
||||
StreamsInbound: 128,
|
||||
StreamsOutbound: 256,
|
||||
Streams: 256,
|
||||
Memory: 128 << 20,
|
||||
FDFraction: 0.25,
|
||||
},
|
||||
|
||||
// Setting the allowlisted limits to be the same as the normal limits. The
|
||||
// allowlist only activates when you reach your normal system/transient
|
||||
// limits. So it's okay if these limits err on the side of being too big,
|
||||
// since most of the time you won't even use any of these. Tune these down
|
||||
// if you want to manage your resources against an allowlisted endpoint.
|
||||
AllowlistedSystemBaseLimit: BaseLimit{
|
||||
ConnsInbound: 64,
|
||||
ConnsOutbound: 128,
|
||||
Conns: 128,
|
||||
StreamsInbound: 64 * 16,
|
||||
StreamsOutbound: 128 * 16,
|
||||
Streams: 128 * 16,
|
||||
Memory: 128 << 20,
|
||||
FD: 256,
|
||||
},
|
||||
|
||||
AllowlistedSystemLimitIncrease: BaseLimitIncrease{
|
||||
ConnsInbound: 64,
|
||||
ConnsOutbound: 128,
|
||||
Conns: 128,
|
||||
StreamsInbound: 64 * 16,
|
||||
StreamsOutbound: 128 * 16,
|
||||
Streams: 128 * 16,
|
||||
Memory: 1 << 30,
|
||||
FDFraction: 1,
|
||||
},
|
||||
|
||||
AllowlistedTransientBaseLimit: BaseLimit{
|
||||
ConnsInbound: 32,
|
||||
ConnsOutbound: 64,
|
||||
Conns: 64,
|
||||
StreamsInbound: 128,
|
||||
StreamsOutbound: 256,
|
||||
Streams: 256,
|
||||
Memory: 32 << 20,
|
||||
FD: 64,
|
||||
},
|
||||
|
||||
AllowlistedTransientLimitIncrease: BaseLimitIncrease{
|
||||
ConnsInbound: 16,
|
||||
ConnsOutbound: 32,
|
||||
Conns: 32,
|
||||
StreamsInbound: 128,
|
||||
StreamsOutbound: 256,
|
||||
Streams: 256,
|
||||
Memory: 128 << 20,
|
||||
FDFraction: 0.25,
|
||||
},
|
||||
|
||||
ServiceBaseLimit: BaseLimit{
|
||||
StreamsInbound: 1024,
|
||||
StreamsOutbound: 4096,
|
||||
Streams: 4096,
|
||||
Memory: 64 << 20,
|
||||
},
|
||||
|
||||
ServiceLimitIncrease: BaseLimitIncrease{
|
||||
StreamsInbound: 512,
|
||||
StreamsOutbound: 2048,
|
||||
Streams: 2048,
|
||||
Memory: 128 << 20,
|
||||
},
|
||||
|
||||
ServicePeerBaseLimit: BaseLimit{
|
||||
StreamsInbound: 128,
|
||||
StreamsOutbound: 256,
|
||||
Streams: 256,
|
||||
Memory: 16 << 20,
|
||||
},
|
||||
|
||||
ServicePeerLimitIncrease: BaseLimitIncrease{
|
||||
StreamsInbound: 4,
|
||||
StreamsOutbound: 8,
|
||||
Streams: 8,
|
||||
Memory: 4 << 20,
|
||||
},
|
||||
|
||||
ProtocolBaseLimit: BaseLimit{
|
||||
StreamsInbound: 512,
|
||||
StreamsOutbound: 2048,
|
||||
Streams: 2048,
|
||||
Memory: 64 << 20,
|
||||
},
|
||||
|
||||
ProtocolLimitIncrease: BaseLimitIncrease{
|
||||
StreamsInbound: 256,
|
||||
StreamsOutbound: 512,
|
||||
Streams: 512,
|
||||
Memory: 164 << 20,
|
||||
},
|
||||
|
||||
ProtocolPeerBaseLimit: BaseLimit{
|
||||
StreamsInbound: 64,
|
||||
StreamsOutbound: 128,
|
||||
Streams: 256,
|
||||
Memory: 16 << 20,
|
||||
},
|
||||
|
||||
ProtocolPeerLimitIncrease: BaseLimitIncrease{
|
||||
StreamsInbound: 4,
|
||||
StreamsOutbound: 8,
|
||||
Streams: 16,
|
||||
Memory: 4,
|
||||
},
|
||||
|
||||
PeerBaseLimit: BaseLimit{
|
||||
// 8 for now so that it matches the number of concurrent dials we may do
|
||||
// in swarm_dial.go. With future smart dialing work we should bring this
|
||||
// down
|
||||
ConnsInbound: 8,
|
||||
ConnsOutbound: 8,
|
||||
Conns: 8,
|
||||
StreamsInbound: 256,
|
||||
StreamsOutbound: 512,
|
||||
Streams: 512,
|
||||
Memory: 64 << 20,
|
||||
FD: 4,
|
||||
},
|
||||
|
||||
PeerLimitIncrease: BaseLimitIncrease{
|
||||
StreamsInbound: 128,
|
||||
StreamsOutbound: 256,
|
||||
Streams: 256,
|
||||
Memory: 128 << 20,
|
||||
FDFraction: 1.0 / 64,
|
||||
},
|
||||
|
||||
ConnBaseLimit: BaseLimit{
|
||||
ConnsInbound: 1,
|
||||
ConnsOutbound: 1,
|
||||
Conns: 1,
|
||||
FD: 1,
|
||||
Memory: 32 << 20,
|
||||
},
|
||||
|
||||
StreamBaseLimit: BaseLimit{
|
||||
StreamsInbound: 1,
|
||||
StreamsOutbound: 1,
|
||||
Streams: 1,
|
||||
Memory: 16 << 20,
|
||||
},
|
||||
}
|
||||
|
||||
var infiniteBaseLimit = BaseLimit{
|
||||
Streams: math.MaxInt,
|
||||
StreamsInbound: math.MaxInt,
|
||||
StreamsOutbound: math.MaxInt,
|
||||
Conns: math.MaxInt,
|
||||
ConnsInbound: math.MaxInt,
|
||||
ConnsOutbound: math.MaxInt,
|
||||
FD: math.MaxInt,
|
||||
Memory: math.MaxInt64,
|
||||
}
|
||||
|
||||
// InfiniteLimits are a limiter configuration that uses unlimited limits, thus effectively not limiting anything.
|
||||
// Keep in mind that the operating system limits the number of file descriptors that an application can use.
|
||||
var InfiniteLimits = ConcreteLimitConfig{
|
||||
system: infiniteBaseLimit,
|
||||
transient: infiniteBaseLimit,
|
||||
allowlistedSystem: infiniteBaseLimit,
|
||||
allowlistedTransient: infiniteBaseLimit,
|
||||
serviceDefault: infiniteBaseLimit,
|
||||
servicePeerDefault: infiniteBaseLimit,
|
||||
protocolDefault: infiniteBaseLimit,
|
||||
protocolPeerDefault: infiniteBaseLimit,
|
||||
peerDefault: infiniteBaseLimit,
|
||||
conn: infiniteBaseLimit,
|
||||
stream: infiniteBaseLimit,
|
||||
}
|
||||
168
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/metrics.go
generated
vendored
Normal file
168
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
)
|
||||
|
||||
// MetricsReporter is an interface for collecting metrics from resource manager actions
|
||||
type MetricsReporter interface {
|
||||
// AllowConn is invoked when opening a connection is allowed
|
||||
AllowConn(dir network.Direction, usefd bool)
|
||||
// BlockConn is invoked when opening a connection is blocked
|
||||
BlockConn(dir network.Direction, usefd bool)
|
||||
|
||||
// AllowStream is invoked when opening a stream is allowed
|
||||
AllowStream(p peer.ID, dir network.Direction)
|
||||
// BlockStream is invoked when opening a stream is blocked
|
||||
BlockStream(p peer.ID, dir network.Direction)
|
||||
|
||||
// AllowPeer is invoked when attaching ac onnection to a peer is allowed
|
||||
AllowPeer(p peer.ID)
|
||||
// BlockPeer is invoked when attaching ac onnection to a peer is blocked
|
||||
BlockPeer(p peer.ID)
|
||||
|
||||
// AllowProtocol is invoked when setting the protocol for a stream is allowed
|
||||
AllowProtocol(proto protocol.ID)
|
||||
// BlockProtocol is invoked when setting the protocol for a stream is blocked
|
||||
BlockProtocol(proto protocol.ID)
|
||||
// BlockProtocolPeer is invoked when setting the protocol for a stream is blocked at the per protocol peer scope
|
||||
BlockProtocolPeer(proto protocol.ID, p peer.ID)
|
||||
|
||||
// AllowService is invoked when setting the protocol for a stream is allowed
|
||||
AllowService(svc string)
|
||||
// BlockService is invoked when setting the protocol for a stream is blocked
|
||||
BlockService(svc string)
|
||||
// BlockServicePeer is invoked when setting the service for a stream is blocked at the per service peer scope
|
||||
BlockServicePeer(svc string, p peer.ID)
|
||||
|
||||
// AllowMemory is invoked when a memory reservation is allowed
|
||||
AllowMemory(size int)
|
||||
// BlockMemory is invoked when a memory reservation is blocked
|
||||
BlockMemory(size int)
|
||||
}
|
||||
|
||||
type metrics struct {
|
||||
reporter MetricsReporter
|
||||
}
|
||||
|
||||
// WithMetrics is a resource manager option to enable metrics collection
|
||||
func WithMetrics(reporter MetricsReporter) Option {
|
||||
return func(r *resourceManager) error {
|
||||
r.metrics = &metrics{reporter: reporter}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *metrics) AllowConn(dir network.Direction, usefd bool) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.AllowConn(dir, usefd)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockConn(dir network.Direction, usefd bool) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockConn(dir, usefd)
|
||||
}
|
||||
|
||||
func (m *metrics) AllowStream(p peer.ID, dir network.Direction) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.AllowStream(p, dir)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockStream(p peer.ID, dir network.Direction) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockStream(p, dir)
|
||||
}
|
||||
|
||||
func (m *metrics) AllowPeer(p peer.ID) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.AllowPeer(p)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockPeer(p peer.ID) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockPeer(p)
|
||||
}
|
||||
|
||||
func (m *metrics) AllowProtocol(proto protocol.ID) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.AllowProtocol(proto)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockProtocol(proto protocol.ID) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockProtocol(proto)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockProtocolPeer(proto protocol.ID, p peer.ID) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockProtocolPeer(proto, p)
|
||||
}
|
||||
|
||||
func (m *metrics) AllowService(svc string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.AllowService(svc)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockService(svc string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockService(svc)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockServicePeer(svc string, p peer.ID) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockServicePeer(svc, p)
|
||||
}
|
||||
|
||||
func (m *metrics) AllowMemory(size int) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.AllowMemory(size)
|
||||
}
|
||||
|
||||
func (m *metrics) BlockMemory(size int) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.reporter.BlockMemory(size)
|
||||
}
|
||||
878
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/rcmgr.go
generated
vendored
Normal file
878
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/rcmgr.go
generated
vendored
Normal file
@@ -0,0 +1,878 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
var log = logging.Logger("rcmgr")
|
||||
|
||||
type resourceManager struct {
|
||||
limits Limiter
|
||||
|
||||
trace *trace
|
||||
metrics *metrics
|
||||
disableMetrics bool
|
||||
|
||||
allowlist *Allowlist
|
||||
|
||||
system *systemScope
|
||||
transient *transientScope
|
||||
|
||||
allowlistedSystem *systemScope
|
||||
allowlistedTransient *transientScope
|
||||
|
||||
cancelCtx context.Context
|
||||
cancel func()
|
||||
wg sync.WaitGroup
|
||||
|
||||
mx sync.Mutex
|
||||
svc map[string]*serviceScope
|
||||
proto map[protocol.ID]*protocolScope
|
||||
peer map[peer.ID]*peerScope
|
||||
|
||||
stickyProto map[protocol.ID]struct{}
|
||||
stickyPeer map[peer.ID]struct{}
|
||||
|
||||
connId, streamId int64
|
||||
}
|
||||
|
||||
var _ network.ResourceManager = (*resourceManager)(nil)
|
||||
|
||||
type systemScope struct {
|
||||
*resourceScope
|
||||
}
|
||||
|
||||
var _ network.ResourceScope = (*systemScope)(nil)
|
||||
|
||||
type transientScope struct {
|
||||
*resourceScope
|
||||
|
||||
system *systemScope
|
||||
}
|
||||
|
||||
var _ network.ResourceScope = (*transientScope)(nil)
|
||||
|
||||
type serviceScope struct {
|
||||
*resourceScope
|
||||
|
||||
service string
|
||||
rcmgr *resourceManager
|
||||
|
||||
peers map[peer.ID]*resourceScope
|
||||
}
|
||||
|
||||
var _ network.ServiceScope = (*serviceScope)(nil)
|
||||
|
||||
type protocolScope struct {
|
||||
*resourceScope
|
||||
|
||||
proto protocol.ID
|
||||
rcmgr *resourceManager
|
||||
|
||||
peers map[peer.ID]*resourceScope
|
||||
}
|
||||
|
||||
var _ network.ProtocolScope = (*protocolScope)(nil)
|
||||
|
||||
type peerScope struct {
|
||||
*resourceScope
|
||||
|
||||
peer peer.ID
|
||||
rcmgr *resourceManager
|
||||
}
|
||||
|
||||
var _ network.PeerScope = (*peerScope)(nil)
|
||||
|
||||
type connectionScope struct {
|
||||
*resourceScope
|
||||
|
||||
dir network.Direction
|
||||
usefd bool
|
||||
isAllowlisted bool
|
||||
rcmgr *resourceManager
|
||||
peer *peerScope
|
||||
endpoint multiaddr.Multiaddr
|
||||
}
|
||||
|
||||
var _ network.ConnScope = (*connectionScope)(nil)
|
||||
var _ network.ConnManagementScope = (*connectionScope)(nil)
|
||||
|
||||
type streamScope struct {
|
||||
*resourceScope
|
||||
|
||||
dir network.Direction
|
||||
rcmgr *resourceManager
|
||||
peer *peerScope
|
||||
svc *serviceScope
|
||||
proto *protocolScope
|
||||
|
||||
peerProtoScope *resourceScope
|
||||
peerSvcScope *resourceScope
|
||||
}
|
||||
|
||||
var _ network.StreamScope = (*streamScope)(nil)
|
||||
var _ network.StreamManagementScope = (*streamScope)(nil)
|
||||
|
||||
type Option func(*resourceManager) error
|
||||
|
||||
func NewResourceManager(limits Limiter, opts ...Option) (network.ResourceManager, error) {
|
||||
allowlist := newAllowlist()
|
||||
r := &resourceManager{
|
||||
limits: limits,
|
||||
allowlist: &allowlist,
|
||||
svc: make(map[string]*serviceScope),
|
||||
proto: make(map[protocol.ID]*protocolScope),
|
||||
peer: make(map[peer.ID]*peerScope),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !r.disableMetrics {
|
||||
var sr TraceReporter
|
||||
sr, err := NewStatsTraceReporter()
|
||||
if err != nil {
|
||||
log.Errorf("failed to initialise StatsTraceReporter %s", err)
|
||||
} else {
|
||||
if r.trace == nil {
|
||||
r.trace = &trace{}
|
||||
}
|
||||
found := false
|
||||
for _, rep := range r.trace.reporters {
|
||||
if rep == sr {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
r.trace.reporters = append(r.trace.reporters, sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.trace.Start(limits); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.system = newSystemScope(limits.GetSystemLimits(), r, "system")
|
||||
r.system.IncRef()
|
||||
r.transient = newTransientScope(limits.GetTransientLimits(), r, "transient", r.system.resourceScope)
|
||||
r.transient.IncRef()
|
||||
|
||||
r.allowlistedSystem = newSystemScope(limits.GetAllowlistedSystemLimits(), r, "allowlistedSystem")
|
||||
r.allowlistedSystem.IncRef()
|
||||
r.allowlistedTransient = newTransientScope(limits.GetAllowlistedTransientLimits(), r, "allowlistedTransient", r.allowlistedSystem.resourceScope)
|
||||
r.allowlistedTransient.IncRef()
|
||||
|
||||
r.cancelCtx, r.cancel = context.WithCancel(context.Background())
|
||||
|
||||
r.wg.Add(1)
|
||||
go r.background()
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *resourceManager) GetAllowlist() *Allowlist {
|
||||
return r.allowlist
|
||||
}
|
||||
|
||||
// GetAllowlist tries to get the allowlist from the given resourcemanager
|
||||
// interface by checking to see if its concrete type is a resourceManager.
|
||||
// Returns nil if it fails to get the allowlist.
|
||||
func GetAllowlist(rcmgr network.ResourceManager) *Allowlist {
|
||||
r, ok := rcmgr.(*resourceManager)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.allowlist
|
||||
}
|
||||
|
||||
func (r *resourceManager) ViewSystem(f func(network.ResourceScope) error) error {
|
||||
return f(r.system)
|
||||
}
|
||||
|
||||
func (r *resourceManager) ViewTransient(f func(network.ResourceScope) error) error {
|
||||
return f(r.transient)
|
||||
}
|
||||
|
||||
func (r *resourceManager) ViewService(srv string, f func(network.ServiceScope) error) error {
|
||||
s := r.getServiceScope(srv)
|
||||
defer s.DecRef()
|
||||
|
||||
return f(s)
|
||||
}
|
||||
|
||||
func (r *resourceManager) ViewProtocol(proto protocol.ID, f func(network.ProtocolScope) error) error {
|
||||
s := r.getProtocolScope(proto)
|
||||
defer s.DecRef()
|
||||
|
||||
return f(s)
|
||||
}
|
||||
|
||||
func (r *resourceManager) ViewPeer(p peer.ID, f func(network.PeerScope) error) error {
|
||||
s := r.getPeerScope(p)
|
||||
defer s.DecRef()
|
||||
|
||||
return f(s)
|
||||
}
|
||||
|
||||
func (r *resourceManager) getServiceScope(svc string) *serviceScope {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
s, ok := r.svc[svc]
|
||||
if !ok {
|
||||
s = newServiceScope(svc, r.limits.GetServiceLimits(svc), r)
|
||||
r.svc[svc] = s
|
||||
}
|
||||
|
||||
s.IncRef()
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *resourceManager) getProtocolScope(proto protocol.ID) *protocolScope {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
s, ok := r.proto[proto]
|
||||
if !ok {
|
||||
s = newProtocolScope(proto, r.limits.GetProtocolLimits(proto), r)
|
||||
r.proto[proto] = s
|
||||
}
|
||||
|
||||
s.IncRef()
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *resourceManager) setStickyProtocol(proto protocol.ID) {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
if r.stickyProto == nil {
|
||||
r.stickyProto = make(map[protocol.ID]struct{})
|
||||
}
|
||||
r.stickyProto[proto] = struct{}{}
|
||||
}
|
||||
|
||||
func (r *resourceManager) getPeerScope(p peer.ID) *peerScope {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
s, ok := r.peer[p]
|
||||
if !ok {
|
||||
s = newPeerScope(p, r.limits.GetPeerLimits(p), r)
|
||||
r.peer[p] = s
|
||||
}
|
||||
|
||||
s.IncRef()
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *resourceManager) setStickyPeer(p peer.ID) {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
if r.stickyPeer == nil {
|
||||
r.stickyPeer = make(map[peer.ID]struct{})
|
||||
}
|
||||
|
||||
r.stickyPeer[p] = struct{}{}
|
||||
}
|
||||
|
||||
func (r *resourceManager) nextConnId() int64 {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
r.connId++
|
||||
return r.connId
|
||||
}
|
||||
|
||||
func (r *resourceManager) nextStreamId() int64 {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
r.streamId++
|
||||
return r.streamId
|
||||
}
|
||||
|
||||
func (r *resourceManager) OpenConnection(dir network.Direction, usefd bool, endpoint multiaddr.Multiaddr) (network.ConnManagementScope, error) {
|
||||
var conn *connectionScope
|
||||
conn = newConnectionScope(dir, usefd, r.limits.GetConnLimits(), r, endpoint)
|
||||
|
||||
err := conn.AddConn(dir, usefd)
|
||||
if err != nil {
|
||||
// Try again if this is an allowlisted connection
|
||||
// Failed to open connection, let's see if this was allowlisted and try again
|
||||
allowed := r.allowlist.Allowed(endpoint)
|
||||
if allowed {
|
||||
conn.Done()
|
||||
conn = newAllowListedConnectionScope(dir, usefd, r.limits.GetConnLimits(), r, endpoint)
|
||||
err = conn.AddConn(dir, usefd)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
conn.Done()
|
||||
r.metrics.BlockConn(dir, usefd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.metrics.AllowConn(dir, usefd)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (r *resourceManager) OpenStream(p peer.ID, dir network.Direction) (network.StreamManagementScope, error) {
|
||||
peer := r.getPeerScope(p)
|
||||
stream := newStreamScope(dir, r.limits.GetStreamLimits(p), peer, r)
|
||||
peer.DecRef() // we have the reference in edges
|
||||
|
||||
err := stream.AddStream(dir)
|
||||
if err != nil {
|
||||
stream.Done()
|
||||
r.metrics.BlockStream(p, dir)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.metrics.AllowStream(p, dir)
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (r *resourceManager) Close() error {
|
||||
r.cancel()
|
||||
r.wg.Wait()
|
||||
r.trace.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resourceManager) background() {
|
||||
defer r.wg.Done()
|
||||
|
||||
// periodically garbage collects unused peer and protocol scopes
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
r.gc()
|
||||
case <-r.cancelCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resourceManager) gc() {
|
||||
r.mx.Lock()
|
||||
defer r.mx.Unlock()
|
||||
|
||||
for proto, s := range r.proto {
|
||||
_, sticky := r.stickyProto[proto]
|
||||
if sticky {
|
||||
continue
|
||||
}
|
||||
if s.IsUnused() {
|
||||
s.Done()
|
||||
delete(r.proto, proto)
|
||||
}
|
||||
}
|
||||
|
||||
var deadPeers []peer.ID
|
||||
for p, s := range r.peer {
|
||||
_, sticky := r.stickyPeer[p]
|
||||
if sticky {
|
||||
continue
|
||||
}
|
||||
|
||||
if s.IsUnused() {
|
||||
s.Done()
|
||||
delete(r.peer, p)
|
||||
deadPeers = append(deadPeers, p)
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range r.svc {
|
||||
s.Lock()
|
||||
for _, p := range deadPeers {
|
||||
ps, ok := s.peers[p]
|
||||
if ok {
|
||||
ps.Done()
|
||||
delete(s.peers, p)
|
||||
}
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
for _, s := range r.proto {
|
||||
s.Lock()
|
||||
for _, p := range deadPeers {
|
||||
ps, ok := s.peers[p]
|
||||
if ok {
|
||||
ps.Done()
|
||||
delete(s.peers, p)
|
||||
}
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func newSystemScope(limit Limit, rcmgr *resourceManager, name string) *systemScope {
|
||||
return &systemScope{
|
||||
resourceScope: newResourceScope(limit, nil, name, rcmgr.trace, rcmgr.metrics),
|
||||
}
|
||||
}
|
||||
|
||||
func newTransientScope(limit Limit, rcmgr *resourceManager, name string, systemScope *resourceScope) *transientScope {
|
||||
return &transientScope{
|
||||
resourceScope: newResourceScope(limit,
|
||||
[]*resourceScope{systemScope},
|
||||
name, rcmgr.trace, rcmgr.metrics),
|
||||
system: rcmgr.system,
|
||||
}
|
||||
}
|
||||
|
||||
func newServiceScope(service string, limit Limit, rcmgr *resourceManager) *serviceScope {
|
||||
return &serviceScope{
|
||||
resourceScope: newResourceScope(limit,
|
||||
[]*resourceScope{rcmgr.system.resourceScope},
|
||||
fmt.Sprintf("service:%s", service), rcmgr.trace, rcmgr.metrics),
|
||||
service: service,
|
||||
rcmgr: rcmgr,
|
||||
}
|
||||
}
|
||||
|
||||
func newProtocolScope(proto protocol.ID, limit Limit, rcmgr *resourceManager) *protocolScope {
|
||||
return &protocolScope{
|
||||
resourceScope: newResourceScope(limit,
|
||||
[]*resourceScope{rcmgr.system.resourceScope},
|
||||
fmt.Sprintf("protocol:%s", proto), rcmgr.trace, rcmgr.metrics),
|
||||
proto: proto,
|
||||
rcmgr: rcmgr,
|
||||
}
|
||||
}
|
||||
|
||||
func newPeerScope(p peer.ID, limit Limit, rcmgr *resourceManager) *peerScope {
|
||||
return &peerScope{
|
||||
resourceScope: newResourceScope(limit,
|
||||
[]*resourceScope{rcmgr.system.resourceScope},
|
||||
peerScopeName(p), rcmgr.trace, rcmgr.metrics),
|
||||
peer: p,
|
||||
rcmgr: rcmgr,
|
||||
}
|
||||
}
|
||||
|
||||
func newConnectionScope(dir network.Direction, usefd bool, limit Limit, rcmgr *resourceManager, endpoint multiaddr.Multiaddr) *connectionScope {
|
||||
return &connectionScope{
|
||||
resourceScope: newResourceScope(limit,
|
||||
[]*resourceScope{rcmgr.transient.resourceScope, rcmgr.system.resourceScope},
|
||||
connScopeName(rcmgr.nextConnId()), rcmgr.trace, rcmgr.metrics),
|
||||
dir: dir,
|
||||
usefd: usefd,
|
||||
rcmgr: rcmgr,
|
||||
endpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
func newAllowListedConnectionScope(dir network.Direction, usefd bool, limit Limit, rcmgr *resourceManager, endpoint multiaddr.Multiaddr) *connectionScope {
|
||||
return &connectionScope{
|
||||
resourceScope: newResourceScope(limit,
|
||||
[]*resourceScope{rcmgr.allowlistedTransient.resourceScope, rcmgr.allowlistedSystem.resourceScope},
|
||||
connScopeName(rcmgr.nextConnId()), rcmgr.trace, rcmgr.metrics),
|
||||
dir: dir,
|
||||
usefd: usefd,
|
||||
rcmgr: rcmgr,
|
||||
endpoint: endpoint,
|
||||
isAllowlisted: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newStreamScope(dir network.Direction, limit Limit, peer *peerScope, rcmgr *resourceManager) *streamScope {
|
||||
return &streamScope{
|
||||
resourceScope: newResourceScope(limit,
|
||||
[]*resourceScope{peer.resourceScope, rcmgr.transient.resourceScope, rcmgr.system.resourceScope},
|
||||
streamScopeName(rcmgr.nextStreamId()), rcmgr.trace, rcmgr.metrics),
|
||||
dir: dir,
|
||||
rcmgr: peer.rcmgr,
|
||||
peer: peer,
|
||||
}
|
||||
}
|
||||
|
||||
func IsSystemScope(name string) bool {
|
||||
return name == "system"
|
||||
}
|
||||
|
||||
func IsTransientScope(name string) bool {
|
||||
return name == "transient"
|
||||
}
|
||||
|
||||
func streamScopeName(streamId int64) string {
|
||||
return fmt.Sprintf("stream-%d", streamId)
|
||||
}
|
||||
|
||||
func IsStreamScope(name string) bool {
|
||||
return strings.HasPrefix(name, "stream-") && !IsSpan(name)
|
||||
}
|
||||
|
||||
func connScopeName(streamId int64) string {
|
||||
return fmt.Sprintf("conn-%d", streamId)
|
||||
}
|
||||
|
||||
func IsConnScope(name string) bool {
|
||||
return strings.HasPrefix(name, "conn-") && !IsSpan(name)
|
||||
}
|
||||
|
||||
func peerScopeName(p peer.ID) string {
|
||||
return fmt.Sprintf("peer:%s", p)
|
||||
}
|
||||
|
||||
// PeerStrInScopeName returns "" if name is not a peerScopeName. Returns a string to avoid allocating a peer ID object
|
||||
func PeerStrInScopeName(name string) string {
|
||||
if !strings.HasPrefix(name, "peer:") || IsSpan(name) {
|
||||
return ""
|
||||
}
|
||||
// Index to avoid allocating a new string
|
||||
peerSplitIdx := strings.Index(name, "peer:")
|
||||
if peerSplitIdx == -1 {
|
||||
return ""
|
||||
}
|
||||
p := (name[peerSplitIdx+len("peer:"):])
|
||||
return p
|
||||
}
|
||||
|
||||
// ParseProtocolScopeName returns the service name if name is a serviceScopeName.
|
||||
// Otherwise returns ""
|
||||
func ParseProtocolScopeName(name string) string {
|
||||
if strings.HasPrefix(name, "protocol:") && !IsSpan(name) {
|
||||
if strings.Contains(name, "peer:") {
|
||||
// This is a protocol peer scope
|
||||
return ""
|
||||
}
|
||||
|
||||
// Index to avoid allocating a new string
|
||||
separatorIdx := strings.Index(name, ":")
|
||||
if separatorIdx == -1 {
|
||||
return ""
|
||||
}
|
||||
return name[separatorIdx+1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *serviceScope) Name() string {
|
||||
return s.service
|
||||
}
|
||||
|
||||
func (s *serviceScope) getPeerScope(p peer.ID) *resourceScope {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
ps, ok := s.peers[p]
|
||||
if ok {
|
||||
ps.IncRef()
|
||||
return ps
|
||||
}
|
||||
|
||||
l := s.rcmgr.limits.GetServicePeerLimits(s.service)
|
||||
|
||||
if s.peers == nil {
|
||||
s.peers = make(map[peer.ID]*resourceScope)
|
||||
}
|
||||
|
||||
ps = newResourceScope(l, nil, fmt.Sprintf("%s.peer:%s", s.name, p), s.rcmgr.trace, s.rcmgr.metrics)
|
||||
s.peers[p] = ps
|
||||
|
||||
ps.IncRef()
|
||||
return ps
|
||||
}
|
||||
|
||||
func (s *protocolScope) Protocol() protocol.ID {
|
||||
return s.proto
|
||||
}
|
||||
|
||||
func (s *protocolScope) getPeerScope(p peer.ID) *resourceScope {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
ps, ok := s.peers[p]
|
||||
if ok {
|
||||
ps.IncRef()
|
||||
return ps
|
||||
}
|
||||
|
||||
l := s.rcmgr.limits.GetProtocolPeerLimits(s.proto)
|
||||
|
||||
if s.peers == nil {
|
||||
s.peers = make(map[peer.ID]*resourceScope)
|
||||
}
|
||||
|
||||
ps = newResourceScope(l, nil, fmt.Sprintf("%s.peer:%s", s.name, p), s.rcmgr.trace, s.rcmgr.metrics)
|
||||
s.peers[p] = ps
|
||||
|
||||
ps.IncRef()
|
||||
return ps
|
||||
}
|
||||
|
||||
func (s *peerScope) Peer() peer.ID {
|
||||
return s.peer
|
||||
}
|
||||
|
||||
func (s *connectionScope) PeerScope() network.PeerScope {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// avoid nil is not nil footgun; go....
|
||||
if s.peer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.peer
|
||||
}
|
||||
|
||||
// transferAllowedToStandard transfers this connection scope from being part of
|
||||
// the allowlist set of scopes to being part of the standard set of scopes.
|
||||
// Happens when we first allowlisted this connection due to its IP, but later
|
||||
// discovered that the peer id not what we expected.
|
||||
func (s *connectionScope) transferAllowedToStandard() (err error) {
|
||||
|
||||
systemScope := s.rcmgr.system.resourceScope
|
||||
transientScope := s.rcmgr.transient.resourceScope
|
||||
|
||||
stat := s.resourceScope.rc.stat()
|
||||
|
||||
for _, scope := range s.edges {
|
||||
scope.ReleaseForChild(stat)
|
||||
scope.DecRef() // removed from edges
|
||||
}
|
||||
s.edges = nil
|
||||
|
||||
if err := systemScope.ReserveForChild(stat); err != nil {
|
||||
return err
|
||||
}
|
||||
systemScope.IncRef()
|
||||
|
||||
// Undo this if we fail later
|
||||
defer func() {
|
||||
if err != nil {
|
||||
systemScope.ReleaseForChild(stat)
|
||||
systemScope.DecRef()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := transientScope.ReserveForChild(stat); err != nil {
|
||||
return err
|
||||
}
|
||||
transientScope.IncRef()
|
||||
|
||||
// Update edges
|
||||
s.edges = []*resourceScope{
|
||||
systemScope,
|
||||
transientScope,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *connectionScope) SetPeer(p peer.ID) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.peer != nil {
|
||||
return fmt.Errorf("connection scope already attached to a peer")
|
||||
}
|
||||
|
||||
system := s.rcmgr.system
|
||||
transient := s.rcmgr.transient
|
||||
|
||||
if s.isAllowlisted {
|
||||
system = s.rcmgr.allowlistedSystem
|
||||
transient = s.rcmgr.allowlistedTransient
|
||||
|
||||
if !s.rcmgr.allowlist.AllowedPeerAndMultiaddr(p, s.endpoint) {
|
||||
s.isAllowlisted = false
|
||||
|
||||
// This is not an allowed peer + multiaddr combination. We need to
|
||||
// transfer this connection to the general scope. We'll do this first by
|
||||
// transferring the connection to the system and transient scopes, then
|
||||
// continue on with this function. The idea is that a connection
|
||||
// shouldn't get the benefit of evading the transient scope because it
|
||||
// was _almost_ an allowlisted connection.
|
||||
if err := s.transferAllowedToStandard(); err != nil {
|
||||
// Failed to transfer this connection to the standard scopes
|
||||
return err
|
||||
}
|
||||
|
||||
// set the system and transient scopes to the non-allowlisted ones
|
||||
system = s.rcmgr.system
|
||||
transient = s.rcmgr.transient
|
||||
}
|
||||
}
|
||||
|
||||
s.peer = s.rcmgr.getPeerScope(p)
|
||||
|
||||
// juggle resources from transient scope to peer scope
|
||||
stat := s.resourceScope.rc.stat()
|
||||
if err := s.peer.ReserveForChild(stat); err != nil {
|
||||
s.peer.DecRef()
|
||||
s.peer = nil
|
||||
s.rcmgr.metrics.BlockPeer(p)
|
||||
return err
|
||||
}
|
||||
|
||||
transient.ReleaseForChild(stat)
|
||||
transient.DecRef() // removed from edges
|
||||
|
||||
// update edges
|
||||
edges := []*resourceScope{
|
||||
s.peer.resourceScope,
|
||||
system.resourceScope,
|
||||
}
|
||||
s.resourceScope.edges = edges
|
||||
|
||||
s.rcmgr.metrics.AllowPeer(p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *streamScope) ProtocolScope() network.ProtocolScope {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// avoid nil is not nil footgun; go....
|
||||
if s.proto == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.proto
|
||||
}
|
||||
|
||||
func (s *streamScope) SetProtocol(proto protocol.ID) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.proto != nil {
|
||||
return fmt.Errorf("stream scope already attached to a protocol")
|
||||
}
|
||||
|
||||
s.proto = s.rcmgr.getProtocolScope(proto)
|
||||
|
||||
// juggle resources from transient scope to protocol scope
|
||||
stat := s.resourceScope.rc.stat()
|
||||
if err := s.proto.ReserveForChild(stat); err != nil {
|
||||
s.proto.DecRef()
|
||||
s.proto = nil
|
||||
s.rcmgr.metrics.BlockProtocol(proto)
|
||||
return err
|
||||
}
|
||||
|
||||
s.peerProtoScope = s.proto.getPeerScope(s.peer.peer)
|
||||
if err := s.peerProtoScope.ReserveForChild(stat); err != nil {
|
||||
s.proto.ReleaseForChild(stat)
|
||||
s.proto.DecRef()
|
||||
s.proto = nil
|
||||
s.peerProtoScope.DecRef()
|
||||
s.peerProtoScope = nil
|
||||
s.rcmgr.metrics.BlockProtocolPeer(proto, s.peer.peer)
|
||||
return err
|
||||
}
|
||||
|
||||
s.rcmgr.transient.ReleaseForChild(stat)
|
||||
s.rcmgr.transient.DecRef() // removed from edges
|
||||
|
||||
// update edges
|
||||
edges := []*resourceScope{
|
||||
s.peer.resourceScope,
|
||||
s.peerProtoScope,
|
||||
s.proto.resourceScope,
|
||||
s.rcmgr.system.resourceScope,
|
||||
}
|
||||
s.resourceScope.edges = edges
|
||||
|
||||
s.rcmgr.metrics.AllowProtocol(proto)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *streamScope) ServiceScope() network.ServiceScope {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// avoid nil is not nil footgun; go....
|
||||
if s.svc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.svc
|
||||
}
|
||||
|
||||
func (s *streamScope) SetService(svc string) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.svc != nil {
|
||||
return fmt.Errorf("stream scope already attached to a service")
|
||||
}
|
||||
if s.proto == nil {
|
||||
return fmt.Errorf("stream scope not attached to a protocol")
|
||||
}
|
||||
|
||||
s.svc = s.rcmgr.getServiceScope(svc)
|
||||
|
||||
// reserve resources in service
|
||||
stat := s.resourceScope.rc.stat()
|
||||
if err := s.svc.ReserveForChild(stat); err != nil {
|
||||
s.svc.DecRef()
|
||||
s.svc = nil
|
||||
s.rcmgr.metrics.BlockService(svc)
|
||||
return err
|
||||
}
|
||||
|
||||
// get the per peer service scope constraint, if any
|
||||
s.peerSvcScope = s.svc.getPeerScope(s.peer.peer)
|
||||
if err := s.peerSvcScope.ReserveForChild(stat); err != nil {
|
||||
s.svc.ReleaseForChild(stat)
|
||||
s.svc.DecRef()
|
||||
s.svc = nil
|
||||
s.peerSvcScope.DecRef()
|
||||
s.peerSvcScope = nil
|
||||
s.rcmgr.metrics.BlockServicePeer(svc, s.peer.peer)
|
||||
return err
|
||||
}
|
||||
|
||||
// update edges
|
||||
edges := []*resourceScope{
|
||||
s.peer.resourceScope,
|
||||
s.peerProtoScope,
|
||||
s.peerSvcScope,
|
||||
s.proto.resourceScope,
|
||||
s.svc.resourceScope,
|
||||
s.rcmgr.system.resourceScope,
|
||||
}
|
||||
s.resourceScope.edges = edges
|
||||
|
||||
s.rcmgr.metrics.AllowService(svc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *streamScope) PeerScope() network.PeerScope {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// avoid nil is not nil footgun; go....
|
||||
if s.peer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.peer
|
||||
}
|
||||
814
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/scope.go
generated
vendored
Normal file
814
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/scope.go
generated
vendored
Normal file
@@ -0,0 +1,814 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
// resources tracks the current state of resource consumption
|
||||
type resources struct {
|
||||
limit Limit
|
||||
|
||||
nconnsIn, nconnsOut int
|
||||
nstreamsIn, nstreamsOut int
|
||||
nfd int
|
||||
|
||||
memory int64
|
||||
}
|
||||
|
||||
// A resourceScope can be a DAG, where a downstream node is not allowed to outlive an upstream node
|
||||
// (ie cannot call Done in the upstream node before the downstream node) and account for resources
|
||||
// using a linearized parent set.
|
||||
// A resourceScope can be a span scope, where it has a specific owner; span scopes create a tree rooted
|
||||
// at the owner (which can be a DAG scope) and can outlive their parents -- this is important because
|
||||
// span scopes are the main *user* interface for memory management, and the user may call
|
||||
// Done in a span scope after the system has closed the root of the span tree in some background
|
||||
// goroutine.
|
||||
// If we didn't make this distinction we would have a double release problem in that case.
|
||||
type resourceScope struct {
|
||||
sync.Mutex
|
||||
done bool
|
||||
refCnt int
|
||||
|
||||
spanID int
|
||||
|
||||
rc resources
|
||||
owner *resourceScope // set in span scopes, which define trees
|
||||
edges []*resourceScope // set in DAG scopes, it's the linearized parent set
|
||||
|
||||
name string // for debugging purposes
|
||||
trace *trace // debug tracing
|
||||
metrics *metrics // metrics collection
|
||||
}
|
||||
|
||||
var _ network.ResourceScope = (*resourceScope)(nil)
|
||||
var _ network.ResourceScopeSpan = (*resourceScope)(nil)
|
||||
|
||||
func newResourceScope(limit Limit, edges []*resourceScope, name string, trace *trace, metrics *metrics) *resourceScope {
|
||||
for _, e := range edges {
|
||||
e.IncRef()
|
||||
}
|
||||
r := &resourceScope{
|
||||
rc: resources{limit: limit},
|
||||
edges: edges,
|
||||
name: name,
|
||||
trace: trace,
|
||||
metrics: metrics,
|
||||
}
|
||||
r.trace.CreateScope(name, limit)
|
||||
return r
|
||||
}
|
||||
|
||||
func newResourceScopeSpan(owner *resourceScope, id int) *resourceScope {
|
||||
r := &resourceScope{
|
||||
rc: resources{limit: owner.rc.limit},
|
||||
owner: owner,
|
||||
name: fmt.Sprintf("%s.span-%d", owner.name, id),
|
||||
trace: owner.trace,
|
||||
metrics: owner.metrics,
|
||||
}
|
||||
r.trace.CreateScope(r.name, r.rc.limit)
|
||||
return r
|
||||
}
|
||||
|
||||
// IsSpan will return true if this name was created by newResourceScopeSpan
|
||||
func IsSpan(name string) bool {
|
||||
return strings.Contains(name, ".span-")
|
||||
}
|
||||
|
||||
func addInt64WithOverflow(a int64, b int64) (c int64, ok bool) {
|
||||
c = a + b
|
||||
return c, (c > a) == (b > 0)
|
||||
}
|
||||
|
||||
// mulInt64WithOverflow checks for overflow in multiplying two int64s. See
|
||||
// https://groups.google.com/g/golang-nuts/c/h5oSN5t3Au4/m/KaNQREhZh0QJ
|
||||
func mulInt64WithOverflow(a, b int64) (c int64, ok bool) {
|
||||
const mostPositive = 1<<63 - 1
|
||||
const mostNegative = -(mostPositive + 1)
|
||||
c = a * b
|
||||
if a == 0 || b == 0 || a == 1 || b == 1 {
|
||||
return c, true
|
||||
}
|
||||
if a == mostNegative || b == mostNegative {
|
||||
return c, false
|
||||
}
|
||||
return c, c/b == a
|
||||
}
|
||||
|
||||
// Resources implementation
|
||||
func (rc *resources) checkMemory(rsvp int64, prio uint8) error {
|
||||
if rsvp < 0 {
|
||||
return fmt.Errorf("can't reserve negative memory. rsvp=%v", rsvp)
|
||||
}
|
||||
|
||||
limit := rc.limit.GetMemoryLimit()
|
||||
if limit == math.MaxInt64 {
|
||||
// Special case where we've set max limits.
|
||||
return nil
|
||||
}
|
||||
|
||||
newmem, addOk := addInt64WithOverflow(rc.memory, rsvp)
|
||||
|
||||
threshold, mulOk := mulInt64WithOverflow(1+int64(prio), limit)
|
||||
if !mulOk {
|
||||
thresholdBig := big.NewInt(limit)
|
||||
thresholdBig = thresholdBig.Mul(thresholdBig, big.NewInt(1+int64(prio)))
|
||||
thresholdBig.Rsh(thresholdBig, 8) // Divide 256
|
||||
if !thresholdBig.IsInt64() {
|
||||
// Shouldn't happen since the threshold can only be <= limit
|
||||
threshold = limit
|
||||
}
|
||||
threshold = thresholdBig.Int64()
|
||||
} else {
|
||||
threshold = threshold / 256
|
||||
}
|
||||
|
||||
if !addOk || newmem > threshold {
|
||||
return &ErrMemoryLimitExceeded{
|
||||
current: rc.memory,
|
||||
attempted: rsvp,
|
||||
limit: limit,
|
||||
priority: prio,
|
||||
err: network.ErrResourceLimitExceeded,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *resources) reserveMemory(size int64, prio uint8) error {
|
||||
if err := rc.checkMemory(size, prio); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc.memory += size
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *resources) releaseMemory(size int64) {
|
||||
rc.memory -= size
|
||||
|
||||
// sanity check for bugs upstream
|
||||
if rc.memory < 0 {
|
||||
log.Warn("BUG: too much memory released")
|
||||
rc.memory = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *resources) addStream(dir network.Direction) error {
|
||||
if dir == network.DirInbound {
|
||||
return rc.addStreams(1, 0)
|
||||
}
|
||||
return rc.addStreams(0, 1)
|
||||
}
|
||||
|
||||
func (rc *resources) addStreams(incount, outcount int) error {
|
||||
if incount > 0 {
|
||||
limit := rc.limit.GetStreamLimit(network.DirInbound)
|
||||
if rc.nstreamsIn+incount > limit {
|
||||
return &ErrStreamOrConnLimitExceeded{
|
||||
current: rc.nstreamsIn,
|
||||
attempted: incount,
|
||||
limit: limit,
|
||||
err: fmt.Errorf("cannot reserve inbound stream: %w", network.ErrResourceLimitExceeded),
|
||||
}
|
||||
}
|
||||
}
|
||||
if outcount > 0 {
|
||||
limit := rc.limit.GetStreamLimit(network.DirOutbound)
|
||||
if rc.nstreamsOut+outcount > limit {
|
||||
return &ErrStreamOrConnLimitExceeded{
|
||||
current: rc.nstreamsOut,
|
||||
attempted: outcount,
|
||||
limit: limit,
|
||||
err: fmt.Errorf("cannot reserve outbound stream: %w", network.ErrResourceLimitExceeded),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if limit := rc.limit.GetStreamTotalLimit(); rc.nstreamsIn+incount+rc.nstreamsOut+outcount > limit {
|
||||
return &ErrStreamOrConnLimitExceeded{
|
||||
current: rc.nstreamsIn + rc.nstreamsOut,
|
||||
attempted: incount + outcount,
|
||||
limit: limit,
|
||||
err: fmt.Errorf("cannot reserve stream: %w", network.ErrResourceLimitExceeded),
|
||||
}
|
||||
}
|
||||
|
||||
rc.nstreamsIn += incount
|
||||
rc.nstreamsOut += outcount
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *resources) removeStream(dir network.Direction) {
|
||||
if dir == network.DirInbound {
|
||||
rc.removeStreams(1, 0)
|
||||
} else {
|
||||
rc.removeStreams(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *resources) removeStreams(incount, outcount int) {
|
||||
rc.nstreamsIn -= incount
|
||||
rc.nstreamsOut -= outcount
|
||||
|
||||
if rc.nstreamsIn < 0 {
|
||||
log.Warn("BUG: too many inbound streams released")
|
||||
rc.nstreamsIn = 0
|
||||
}
|
||||
if rc.nstreamsOut < 0 {
|
||||
log.Warn("BUG: too many outbound streams released")
|
||||
rc.nstreamsOut = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *resources) addConn(dir network.Direction, usefd bool) error {
|
||||
var fd int
|
||||
if usefd {
|
||||
fd = 1
|
||||
}
|
||||
|
||||
if dir == network.DirInbound {
|
||||
return rc.addConns(1, 0, fd)
|
||||
}
|
||||
|
||||
return rc.addConns(0, 1, fd)
|
||||
}
|
||||
|
||||
func (rc *resources) addConns(incount, outcount, fdcount int) error {
|
||||
if incount > 0 {
|
||||
limit := rc.limit.GetConnLimit(network.DirInbound)
|
||||
if rc.nconnsIn+incount > limit {
|
||||
return &ErrStreamOrConnLimitExceeded{
|
||||
current: rc.nconnsIn,
|
||||
attempted: incount,
|
||||
limit: limit,
|
||||
err: fmt.Errorf("cannot reserve inbound connection: %w", network.ErrResourceLimitExceeded),
|
||||
}
|
||||
}
|
||||
}
|
||||
if outcount > 0 {
|
||||
limit := rc.limit.GetConnLimit(network.DirOutbound)
|
||||
if rc.nconnsOut+outcount > limit {
|
||||
return &ErrStreamOrConnLimitExceeded{
|
||||
current: rc.nconnsOut,
|
||||
attempted: outcount,
|
||||
limit: limit,
|
||||
err: fmt.Errorf("cannot reserve outbound connection: %w", network.ErrResourceLimitExceeded),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if connLimit := rc.limit.GetConnTotalLimit(); rc.nconnsIn+incount+rc.nconnsOut+outcount > connLimit {
|
||||
return &ErrStreamOrConnLimitExceeded{
|
||||
current: rc.nconnsIn + rc.nconnsOut,
|
||||
attempted: incount + outcount,
|
||||
limit: connLimit,
|
||||
err: fmt.Errorf("cannot reserve connection: %w", network.ErrResourceLimitExceeded),
|
||||
}
|
||||
}
|
||||
if fdcount > 0 {
|
||||
limit := rc.limit.GetFDLimit()
|
||||
if rc.nfd+fdcount > limit {
|
||||
return &ErrStreamOrConnLimitExceeded{
|
||||
current: rc.nfd,
|
||||
attempted: fdcount,
|
||||
limit: limit,
|
||||
err: fmt.Errorf("cannot reserve file descriptor: %w", network.ErrResourceLimitExceeded),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc.nconnsIn += incount
|
||||
rc.nconnsOut += outcount
|
||||
rc.nfd += fdcount
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *resources) removeConn(dir network.Direction, usefd bool) {
|
||||
var fd int
|
||||
if usefd {
|
||||
fd = 1
|
||||
}
|
||||
|
||||
if dir == network.DirInbound {
|
||||
rc.removeConns(1, 0, fd)
|
||||
} else {
|
||||
rc.removeConns(0, 1, fd)
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *resources) removeConns(incount, outcount, fdcount int) {
|
||||
rc.nconnsIn -= incount
|
||||
rc.nconnsOut -= outcount
|
||||
rc.nfd -= fdcount
|
||||
|
||||
if rc.nconnsIn < 0 {
|
||||
log.Warn("BUG: too many inbound connections released")
|
||||
rc.nconnsIn = 0
|
||||
}
|
||||
if rc.nconnsOut < 0 {
|
||||
log.Warn("BUG: too many outbound connections released")
|
||||
rc.nconnsOut = 0
|
||||
}
|
||||
if rc.nfd < 0 {
|
||||
log.Warn("BUG: too many file descriptors released")
|
||||
rc.nfd = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *resources) stat() network.ScopeStat {
|
||||
return network.ScopeStat{
|
||||
Memory: rc.memory,
|
||||
NumStreamsInbound: rc.nstreamsIn,
|
||||
NumStreamsOutbound: rc.nstreamsOut,
|
||||
NumConnsInbound: rc.nconnsIn,
|
||||
NumConnsOutbound: rc.nconnsOut,
|
||||
NumFD: rc.nfd,
|
||||
}
|
||||
}
|
||||
|
||||
// resourceScope implementation
|
||||
func (s *resourceScope) wrapError(err error) error {
|
||||
return fmt.Errorf("%s: %w", s.name, err)
|
||||
}
|
||||
|
||||
func (s *resourceScope) ReserveMemory(size int, prio uint8) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
if err := s.rc.reserveMemory(int64(size), prio); err != nil {
|
||||
log.Debugw("blocked memory reservation", logValuesMemoryLimit(s.name, "", s.rc.stat(), err)...)
|
||||
s.trace.BlockReserveMemory(s.name, prio, int64(size), s.rc.memory)
|
||||
s.metrics.BlockMemory(size)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
if err := s.reserveMemoryForEdges(size, prio); err != nil {
|
||||
s.rc.releaseMemory(int64(size))
|
||||
s.metrics.BlockMemory(size)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
s.trace.ReserveMemory(s.name, prio, int64(size), s.rc.memory)
|
||||
s.metrics.AllowMemory(size)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) reserveMemoryForEdges(size int, prio uint8) error {
|
||||
if s.owner != nil {
|
||||
return s.owner.ReserveMemory(size, prio)
|
||||
}
|
||||
|
||||
var reserved int
|
||||
var err error
|
||||
for _, e := range s.edges {
|
||||
var stat network.ScopeStat
|
||||
stat, err = e.ReserveMemoryForChild(int64(size), prio)
|
||||
if err != nil {
|
||||
log.Debugw("blocked memory reservation from constraining edge", logValuesMemoryLimit(s.name, e.name, stat, err)...)
|
||||
break
|
||||
}
|
||||
|
||||
reserved++
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// we failed because of a constraint; undo memory reservations
|
||||
for _, e := range s.edges[:reserved] {
|
||||
e.ReleaseMemoryForChild(int64(size))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *resourceScope) releaseMemoryForEdges(size int) {
|
||||
if s.owner != nil {
|
||||
s.owner.ReleaseMemory(size)
|
||||
return
|
||||
}
|
||||
|
||||
for _, e := range s.edges {
|
||||
e.ReleaseMemoryForChild(int64(size))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resourceScope) ReserveMemoryForChild(size int64, prio uint8) (network.ScopeStat, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
if err := s.rc.reserveMemory(size, prio); err != nil {
|
||||
s.trace.BlockReserveMemory(s.name, prio, size, s.rc.memory)
|
||||
return s.rc.stat(), s.wrapError(err)
|
||||
}
|
||||
|
||||
s.trace.ReserveMemory(s.name, prio, size, s.rc.memory)
|
||||
return network.ScopeStat{}, nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) ReleaseMemory(size int) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.releaseMemory(int64(size))
|
||||
s.releaseMemoryForEdges(size)
|
||||
s.trace.ReleaseMemory(s.name, int64(size), s.rc.memory)
|
||||
}
|
||||
|
||||
func (s *resourceScope) ReleaseMemoryForChild(size int64) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.releaseMemory(size)
|
||||
s.trace.ReleaseMemory(s.name, size, s.rc.memory)
|
||||
}
|
||||
|
||||
func (s *resourceScope) AddStream(dir network.Direction) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
if err := s.rc.addStream(dir); err != nil {
|
||||
log.Debugw("blocked stream", logValuesStreamLimit(s.name, "", dir, s.rc.stat(), err)...)
|
||||
s.trace.BlockAddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
if err := s.addStreamForEdges(dir); err != nil {
|
||||
s.rc.removeStream(dir)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
s.trace.AddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) addStreamForEdges(dir network.Direction) error {
|
||||
if s.owner != nil {
|
||||
return s.owner.AddStream(dir)
|
||||
}
|
||||
|
||||
var err error
|
||||
var reserved int
|
||||
for _, e := range s.edges {
|
||||
var stat network.ScopeStat
|
||||
stat, err = e.AddStreamForChild(dir)
|
||||
if err != nil {
|
||||
log.Debugw("blocked stream from constraining edge", logValuesStreamLimit(s.name, e.name, dir, stat, err)...)
|
||||
break
|
||||
}
|
||||
reserved++
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
for _, e := range s.edges[:reserved] {
|
||||
e.RemoveStreamForChild(dir)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *resourceScope) AddStreamForChild(dir network.Direction) (network.ScopeStat, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
if err := s.rc.addStream(dir); err != nil {
|
||||
s.trace.BlockAddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
return s.rc.stat(), s.wrapError(err)
|
||||
}
|
||||
|
||||
s.trace.AddStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
return network.ScopeStat{}, nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) RemoveStream(dir network.Direction) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.removeStream(dir)
|
||||
s.removeStreamForEdges(dir)
|
||||
s.trace.RemoveStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
}
|
||||
|
||||
func (s *resourceScope) removeStreamForEdges(dir network.Direction) {
|
||||
if s.owner != nil {
|
||||
s.owner.RemoveStream(dir)
|
||||
return
|
||||
}
|
||||
|
||||
for _, e := range s.edges {
|
||||
e.RemoveStreamForChild(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resourceScope) RemoveStreamForChild(dir network.Direction) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.removeStream(dir)
|
||||
s.trace.RemoveStream(s.name, dir, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
}
|
||||
|
||||
func (s *resourceScope) AddConn(dir network.Direction, usefd bool) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
if err := s.rc.addConn(dir, usefd); err != nil {
|
||||
log.Debugw("blocked connection", logValuesConnLimit(s.name, "", dir, usefd, s.rc.stat(), err)...)
|
||||
s.trace.BlockAddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
if err := s.addConnForEdges(dir, usefd); err != nil {
|
||||
s.rc.removeConn(dir, usefd)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
s.trace.AddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) addConnForEdges(dir network.Direction, usefd bool) error {
|
||||
if s.owner != nil {
|
||||
return s.owner.AddConn(dir, usefd)
|
||||
}
|
||||
|
||||
var err error
|
||||
var reserved int
|
||||
for _, e := range s.edges {
|
||||
var stat network.ScopeStat
|
||||
stat, err = e.AddConnForChild(dir, usefd)
|
||||
if err != nil {
|
||||
log.Debugw("blocked connection from constraining edge", logValuesConnLimit(s.name, e.name, dir, usefd, stat, err)...)
|
||||
break
|
||||
}
|
||||
reserved++
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
for _, e := range s.edges[:reserved] {
|
||||
e.RemoveConnForChild(dir, usefd)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *resourceScope) AddConnForChild(dir network.Direction, usefd bool) (network.ScopeStat, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return s.rc.stat(), s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
if err := s.rc.addConn(dir, usefd); err != nil {
|
||||
s.trace.BlockAddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
return s.rc.stat(), s.wrapError(err)
|
||||
}
|
||||
|
||||
s.trace.AddConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
return network.ScopeStat{}, nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) RemoveConn(dir network.Direction, usefd bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.removeConn(dir, usefd)
|
||||
s.removeConnForEdges(dir, usefd)
|
||||
s.trace.RemoveConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
}
|
||||
|
||||
func (s *resourceScope) removeConnForEdges(dir network.Direction, usefd bool) {
|
||||
if s.owner != nil {
|
||||
s.owner.RemoveConn(dir, usefd)
|
||||
}
|
||||
|
||||
for _, e := range s.edges {
|
||||
e.RemoveConnForChild(dir, usefd)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resourceScope) RemoveConnForChild(dir network.Direction, usefd bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.removeConn(dir, usefd)
|
||||
s.trace.RemoveConn(s.name, dir, usefd, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
}
|
||||
|
||||
func (s *resourceScope) ReserveForChild(st network.ScopeStat) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
if err := s.rc.reserveMemory(st.Memory, network.ReservationPriorityAlways); err != nil {
|
||||
s.trace.BlockReserveMemory(s.name, 255, st.Memory, s.rc.memory)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
if err := s.rc.addStreams(st.NumStreamsInbound, st.NumStreamsOutbound); err != nil {
|
||||
s.trace.BlockAddStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
s.rc.releaseMemory(st.Memory)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
if err := s.rc.addConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD); err != nil {
|
||||
s.trace.BlockAddConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
|
||||
s.rc.releaseMemory(st.Memory)
|
||||
s.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)
|
||||
return s.wrapError(err)
|
||||
}
|
||||
|
||||
s.trace.ReserveMemory(s.name, 255, st.Memory, s.rc.memory)
|
||||
s.trace.AddStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
s.trace.AddConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) ReleaseForChild(st network.ScopeStat) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.releaseMemory(st.Memory)
|
||||
s.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)
|
||||
s.rc.removeConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD)
|
||||
|
||||
s.trace.ReleaseMemory(s.name, st.Memory, s.rc.memory)
|
||||
s.trace.RemoveStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
s.trace.RemoveConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
}
|
||||
|
||||
func (s *resourceScope) ReleaseResources(st network.ScopeStat) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
s.rc.releaseMemory(st.Memory)
|
||||
s.rc.removeStreams(st.NumStreamsInbound, st.NumStreamsOutbound)
|
||||
s.rc.removeConns(st.NumConnsInbound, st.NumConnsOutbound, st.NumFD)
|
||||
|
||||
if s.owner != nil {
|
||||
s.owner.ReleaseResources(st)
|
||||
} else {
|
||||
for _, e := range s.edges {
|
||||
e.ReleaseForChild(st)
|
||||
}
|
||||
}
|
||||
|
||||
s.trace.ReleaseMemory(s.name, st.Memory, s.rc.memory)
|
||||
s.trace.RemoveStreams(s.name, st.NumStreamsInbound, st.NumStreamsOutbound, s.rc.nstreamsIn, s.rc.nstreamsOut)
|
||||
s.trace.RemoveConns(s.name, st.NumConnsInbound, st.NumConnsOutbound, st.NumFD, s.rc.nconnsIn, s.rc.nconnsOut, s.rc.nfd)
|
||||
}
|
||||
|
||||
func (s *resourceScope) nextSpanID() int {
|
||||
s.spanID++
|
||||
return s.spanID
|
||||
}
|
||||
|
||||
func (s *resourceScope) BeginSpan() (network.ResourceScopeSpan, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return nil, s.wrapError(network.ErrResourceScopeClosed)
|
||||
}
|
||||
|
||||
s.refCnt++
|
||||
return newResourceScopeSpan(s, s.nextSpanID()), nil
|
||||
}
|
||||
|
||||
func (s *resourceScope) Done() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return
|
||||
}
|
||||
|
||||
stat := s.rc.stat()
|
||||
if s.owner != nil {
|
||||
s.owner.ReleaseResources(stat)
|
||||
s.owner.DecRef()
|
||||
} else {
|
||||
for _, e := range s.edges {
|
||||
e.ReleaseForChild(stat)
|
||||
e.DecRef()
|
||||
}
|
||||
}
|
||||
|
||||
s.rc.nstreamsIn = 0
|
||||
s.rc.nstreamsOut = 0
|
||||
s.rc.nconnsIn = 0
|
||||
s.rc.nconnsOut = 0
|
||||
s.rc.nfd = 0
|
||||
s.rc.memory = 0
|
||||
|
||||
s.done = true
|
||||
|
||||
s.trace.DestroyScope(s.name)
|
||||
}
|
||||
|
||||
func (s *resourceScope) Stat() network.ScopeStat {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
return s.rc.stat()
|
||||
}
|
||||
|
||||
func (s *resourceScope) IncRef() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.refCnt++
|
||||
}
|
||||
|
||||
func (s *resourceScope) DecRef() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.refCnt--
|
||||
}
|
||||
|
||||
func (s *resourceScope) IsUnused() bool {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if s.done {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.refCnt > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
st := s.rc.stat()
|
||||
return st.NumStreamsInbound == 0 &&
|
||||
st.NumStreamsOutbound == 0 &&
|
||||
st.NumConnsInbound == 0 &&
|
||||
st.NumConnsOutbound == 0 &&
|
||||
st.NumFD == 0
|
||||
}
|
||||
390
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/stats.go
generated
vendored
Normal file
390
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/stats.go
generated
vendored
Normal file
@@ -0,0 +1,390 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/libp2p/go-libp2p/p2p/metricshelper"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const metricNamespace = "libp2p_rcmgr"
|
||||
|
||||
var (
|
||||
|
||||
// Conns
|
||||
conns = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "connections",
|
||||
Help: "Number of Connections",
|
||||
}, []string{"dir", "scope"})
|
||||
|
||||
connsInboundSystem = conns.With(prometheus.Labels{"dir": "inbound", "scope": "system"})
|
||||
connsInboundTransient = conns.With(prometheus.Labels{"dir": "inbound", "scope": "transient"})
|
||||
connsOutboundSystem = conns.With(prometheus.Labels{"dir": "outbound", "scope": "system"})
|
||||
connsOutboundTransient = conns.With(prometheus.Labels{"dir": "outbound", "scope": "transient"})
|
||||
|
||||
oneTenThenExpDistributionBuckets = []float64{
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 32, 64, 128, 256,
|
||||
}
|
||||
|
||||
// PeerConns
|
||||
peerConns = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "peer_connections",
|
||||
Buckets: oneTenThenExpDistributionBuckets,
|
||||
Help: "Number of connections this peer has",
|
||||
}, []string{"dir"})
|
||||
peerConnsInbound = peerConns.With(prometheus.Labels{"dir": "inbound"})
|
||||
peerConnsOutbound = peerConns.With(prometheus.Labels{"dir": "outbound"})
|
||||
|
||||
// Lets us build a histogram of our current state. See https://github.com/libp2p/go-libp2p-resource-manager/pull/54#discussion_r911244757 for more information.
|
||||
previousPeerConns = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "previous_peer_connections",
|
||||
Buckets: oneTenThenExpDistributionBuckets,
|
||||
Help: "Number of connections this peer previously had. This is used to get the current connection number per peer histogram by subtracting this from the peer_connections histogram",
|
||||
}, []string{"dir"})
|
||||
previousPeerConnsInbound = previousPeerConns.With(prometheus.Labels{"dir": "inbound"})
|
||||
previousPeerConnsOutbound = previousPeerConns.With(prometheus.Labels{"dir": "outbound"})
|
||||
|
||||
// Streams
|
||||
streams = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "streams",
|
||||
Help: "Number of Streams",
|
||||
}, []string{"dir", "scope", "protocol"})
|
||||
|
||||
peerStreams = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "peer_streams",
|
||||
Buckets: oneTenThenExpDistributionBuckets,
|
||||
Help: "Number of streams this peer has",
|
||||
}, []string{"dir"})
|
||||
peerStreamsInbound = peerStreams.With(prometheus.Labels{"dir": "inbound"})
|
||||
peerStreamsOutbound = peerStreams.With(prometheus.Labels{"dir": "outbound"})
|
||||
|
||||
previousPeerStreams = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "previous_peer_streams",
|
||||
Buckets: oneTenThenExpDistributionBuckets,
|
||||
Help: "Number of streams this peer has",
|
||||
}, []string{"dir"})
|
||||
previousPeerStreamsInbound = previousPeerStreams.With(prometheus.Labels{"dir": "inbound"})
|
||||
previousPeerStreamsOutbound = previousPeerStreams.With(prometheus.Labels{"dir": "outbound"})
|
||||
|
||||
// Memory
|
||||
memoryTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "memory",
|
||||
Help: "Amount of memory reserved as reported to the Resource Manager",
|
||||
}, []string{"scope", "protocol"})
|
||||
|
||||
// PeerMemory
|
||||
peerMemory = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "peer_memory",
|
||||
Buckets: memDistribution,
|
||||
Help: "How many peers have reserved this bucket of memory, as reported to the Resource Manager",
|
||||
})
|
||||
previousPeerMemory = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "previous_peer_memory",
|
||||
Buckets: memDistribution,
|
||||
Help: "How many peers have previously reserved this bucket of memory, as reported to the Resource Manager",
|
||||
})
|
||||
|
||||
// ConnMemory
|
||||
connMemory = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "conn_memory",
|
||||
Buckets: memDistribution,
|
||||
Help: "How many conns have reserved this bucket of memory, as reported to the Resource Manager",
|
||||
})
|
||||
previousConnMemory = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "previous_conn_memory",
|
||||
Buckets: memDistribution,
|
||||
Help: "How many conns have previously reserved this bucket of memory, as reported to the Resource Manager",
|
||||
})
|
||||
|
||||
// FDs
|
||||
fds = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "fds",
|
||||
Help: "Number of file descriptors reserved as reported to the Resource Manager",
|
||||
}, []string{"scope"})
|
||||
|
||||
fdsSystem = fds.With(prometheus.Labels{"scope": "system"})
|
||||
fdsTransient = fds.With(prometheus.Labels{"scope": "transient"})
|
||||
|
||||
// Blocked resources
|
||||
blockedResources = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: metricNamespace,
|
||||
Name: "blocked_resources",
|
||||
Help: "Number of blocked resources",
|
||||
}, []string{"dir", "scope", "resource"})
|
||||
)
|
||||
|
||||
var (
|
||||
memDistribution = []float64{
|
||||
1 << 10, // 1KB
|
||||
4 << 10, // 4KB
|
||||
32 << 10, // 32KB
|
||||
1 << 20, // 1MB
|
||||
32 << 20, // 32MB
|
||||
256 << 20, // 256MB
|
||||
512 << 20, // 512MB
|
||||
1 << 30, // 1GB
|
||||
2 << 30, // 2GB
|
||||
4 << 30, // 4GB
|
||||
}
|
||||
)
|
||||
|
||||
func MustRegisterWith(reg prometheus.Registerer) {
|
||||
metricshelper.RegisterCollectors(reg,
|
||||
conns,
|
||||
peerConns,
|
||||
previousPeerConns,
|
||||
streams,
|
||||
peerStreams,
|
||||
|
||||
previousPeerStreams,
|
||||
|
||||
memoryTotal,
|
||||
peerMemory,
|
||||
previousPeerMemory,
|
||||
connMemory,
|
||||
previousConnMemory,
|
||||
fds,
|
||||
blockedResources,
|
||||
)
|
||||
}
|
||||
|
||||
func WithMetricsDisabled() Option {
|
||||
return func(r *resourceManager) error {
|
||||
r.disableMetrics = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// StatsTraceReporter reports stats on the resource manager using its traces.
|
||||
type StatsTraceReporter struct{}
|
||||
|
||||
func NewStatsTraceReporter() (StatsTraceReporter, error) {
|
||||
// TODO tell prometheus the system limits
|
||||
return StatsTraceReporter{}, nil
|
||||
}
|
||||
|
||||
func (r StatsTraceReporter) ConsumeEvent(evt TraceEvt) {
|
||||
tags := metricshelper.GetStringSlice()
|
||||
defer metricshelper.PutStringSlice(tags)
|
||||
|
||||
r.consumeEventWithLabelSlice(evt, tags)
|
||||
}
|
||||
|
||||
// Separate func so that we can test that this function does not allocate. The syncPool may allocate.
|
||||
func (r StatsTraceReporter) consumeEventWithLabelSlice(evt TraceEvt, tags *[]string) {
|
||||
switch evt.Type {
|
||||
case TraceAddStreamEvt, TraceRemoveStreamEvt:
|
||||
if p := PeerStrInScopeName(evt.Name); p != "" {
|
||||
// Aggregated peer stats. Counts how many peers have N number of streams open.
|
||||
// Uses two buckets aggregations. One to count how many streams the
|
||||
// peer has now. The other to count the negative value, or how many
|
||||
// streams did the peer use to have. When looking at the data you
|
||||
// take the difference from the two.
|
||||
|
||||
oldStreamsOut := int64(evt.StreamsOut - evt.DeltaOut)
|
||||
peerStreamsOut := int64(evt.StreamsOut)
|
||||
if oldStreamsOut != peerStreamsOut {
|
||||
if oldStreamsOut != 0 {
|
||||
previousPeerStreamsOutbound.Observe(float64(oldStreamsOut))
|
||||
}
|
||||
if peerStreamsOut != 0 {
|
||||
peerStreamsOutbound.Observe(float64(peerStreamsOut))
|
||||
}
|
||||
}
|
||||
|
||||
oldStreamsIn := int64(evt.StreamsIn - evt.DeltaIn)
|
||||
peerStreamsIn := int64(evt.StreamsIn)
|
||||
if oldStreamsIn != peerStreamsIn {
|
||||
if oldStreamsIn != 0 {
|
||||
previousPeerStreamsInbound.Observe(float64(oldStreamsIn))
|
||||
}
|
||||
if peerStreamsIn != 0 {
|
||||
peerStreamsInbound.Observe(float64(peerStreamsIn))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if evt.DeltaOut != 0 {
|
||||
if IsSystemScope(evt.Name) || IsTransientScope(evt.Name) {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "outbound", evt.Name, "")
|
||||
streams.WithLabelValues(*tags...).Set(float64(evt.StreamsOut))
|
||||
} else if proto := ParseProtocolScopeName(evt.Name); proto != "" {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "outbound", "protocol", proto)
|
||||
streams.WithLabelValues(*tags...).Set(float64(evt.StreamsOut))
|
||||
} else {
|
||||
// Not measuring service scope, connscope, servicepeer and protocolpeer. Lots of data, and
|
||||
// you can use aggregated peer stats + service stats to infer
|
||||
// this.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if evt.DeltaIn != 0 {
|
||||
if IsSystemScope(evt.Name) || IsTransientScope(evt.Name) {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "inbound", evt.Name, "")
|
||||
streams.WithLabelValues(*tags...).Set(float64(evt.StreamsIn))
|
||||
} else if proto := ParseProtocolScopeName(evt.Name); proto != "" {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "inbound", "protocol", proto)
|
||||
streams.WithLabelValues(*tags...).Set(float64(evt.StreamsIn))
|
||||
} else {
|
||||
// Not measuring service scope, connscope, servicepeer and protocolpeer. Lots of data, and
|
||||
// you can use aggregated peer stats + service stats to infer
|
||||
// this.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case TraceAddConnEvt, TraceRemoveConnEvt:
|
||||
if p := PeerStrInScopeName(evt.Name); p != "" {
|
||||
// Aggregated peer stats. Counts how many peers have N number of connections.
|
||||
// Uses two buckets aggregations. One to count how many streams the
|
||||
// peer has now. The other to count the negative value, or how many
|
||||
// conns did the peer use to have. When looking at the data you
|
||||
// take the difference from the two.
|
||||
|
||||
oldConnsOut := int64(evt.ConnsOut - evt.DeltaOut)
|
||||
connsOut := int64(evt.ConnsOut)
|
||||
if oldConnsOut != connsOut {
|
||||
if oldConnsOut != 0 {
|
||||
previousPeerConnsOutbound.Observe(float64(oldConnsOut))
|
||||
}
|
||||
if connsOut != 0 {
|
||||
peerConnsOutbound.Observe(float64(connsOut))
|
||||
}
|
||||
}
|
||||
|
||||
oldConnsIn := int64(evt.ConnsIn - evt.DeltaIn)
|
||||
connsIn := int64(evt.ConnsIn)
|
||||
if oldConnsIn != connsIn {
|
||||
if oldConnsIn != 0 {
|
||||
previousPeerConnsInbound.Observe(float64(oldConnsIn))
|
||||
}
|
||||
if connsIn != 0 {
|
||||
peerConnsInbound.Observe(float64(connsIn))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if IsConnScope(evt.Name) {
|
||||
// Not measuring this. I don't think it's useful.
|
||||
break
|
||||
}
|
||||
|
||||
if IsSystemScope(evt.Name) {
|
||||
connsInboundSystem.Set(float64(evt.ConnsIn))
|
||||
connsOutboundSystem.Set(float64(evt.ConnsOut))
|
||||
} else if IsTransientScope(evt.Name) {
|
||||
connsInboundTransient.Set(float64(evt.ConnsIn))
|
||||
connsOutboundTransient.Set(float64(evt.ConnsOut))
|
||||
}
|
||||
|
||||
// Represents the delta in fds
|
||||
if evt.Delta != 0 {
|
||||
if IsSystemScope(evt.Name) {
|
||||
fdsSystem.Set(float64(evt.FD))
|
||||
} else if IsTransientScope(evt.Name) {
|
||||
fdsTransient.Set(float64(evt.FD))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case TraceReserveMemoryEvt, TraceReleaseMemoryEvt:
|
||||
if p := PeerStrInScopeName(evt.Name); p != "" {
|
||||
oldMem := evt.Memory - evt.Delta
|
||||
if oldMem != evt.Memory {
|
||||
if oldMem != 0 {
|
||||
previousPeerMemory.Observe(float64(oldMem))
|
||||
}
|
||||
if evt.Memory != 0 {
|
||||
peerMemory.Observe(float64(evt.Memory))
|
||||
}
|
||||
}
|
||||
} else if IsConnScope(evt.Name) {
|
||||
oldMem := evt.Memory - evt.Delta
|
||||
if oldMem != evt.Memory {
|
||||
if oldMem != 0 {
|
||||
previousConnMemory.Observe(float64(oldMem))
|
||||
}
|
||||
if evt.Memory != 0 {
|
||||
connMemory.Observe(float64(evt.Memory))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if IsSystemScope(evt.Name) || IsTransientScope(evt.Name) {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, evt.Name, "")
|
||||
memoryTotal.WithLabelValues(*tags...).Set(float64(evt.Memory))
|
||||
} else if proto := ParseProtocolScopeName(evt.Name); proto != "" {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "protocol", proto)
|
||||
memoryTotal.WithLabelValues(*tags...).Set(float64(evt.Memory))
|
||||
} else {
|
||||
// Not measuring connscope, servicepeer and protocolpeer. Lots of data, and
|
||||
// you can use aggregated peer stats + service stats to infer
|
||||
// this.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case TraceBlockAddConnEvt, TraceBlockAddStreamEvt, TraceBlockReserveMemoryEvt:
|
||||
var resource string
|
||||
if evt.Type == TraceBlockAddConnEvt {
|
||||
resource = "connection"
|
||||
} else if evt.Type == TraceBlockAddStreamEvt {
|
||||
resource = "stream"
|
||||
} else {
|
||||
resource = "memory"
|
||||
}
|
||||
|
||||
scopeName := evt.Name
|
||||
// Only the top scopeName. We don't want to get the peerid here.
|
||||
// Using indexes and slices to avoid allocating.
|
||||
scopeSplitIdx := strings.IndexByte(scopeName, ':')
|
||||
if scopeSplitIdx != -1 {
|
||||
scopeName = evt.Name[0:scopeSplitIdx]
|
||||
}
|
||||
// Drop the connection or stream id
|
||||
idSplitIdx := strings.IndexByte(scopeName, '-')
|
||||
if idSplitIdx != -1 {
|
||||
scopeName = scopeName[0:idSplitIdx]
|
||||
}
|
||||
|
||||
if evt.DeltaIn != 0 {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "inbound", scopeName, resource)
|
||||
blockedResources.WithLabelValues(*tags...).Add(float64(evt.DeltaIn))
|
||||
}
|
||||
|
||||
if evt.DeltaOut != 0 {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "outbound", scopeName, resource)
|
||||
blockedResources.WithLabelValues(*tags...).Add(float64(evt.DeltaOut))
|
||||
}
|
||||
|
||||
if evt.Delta != 0 && resource == "connection" {
|
||||
// This represents fds blocked
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "", scopeName, "fd")
|
||||
blockedResources.WithLabelValues(*tags...).Add(float64(evt.Delta))
|
||||
} else if evt.Delta != 0 {
|
||||
*tags = (*tags)[:0]
|
||||
*tags = append(*tags, "", scopeName, resource)
|
||||
blockedResources.WithLabelValues(*tags...).Add(float64(evt.Delta))
|
||||
}
|
||||
}
|
||||
}
|
||||
11
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/sys_not_unix.go
generated
vendored
Normal file
11
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/sys_not_unix.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build !linux && !darwin && !windows
|
||||
|
||||
package rcmgr
|
||||
|
||||
import "runtime"
|
||||
|
||||
// TODO: figure out how to get the number of file descriptors on Windows and other systems
|
||||
func getNumFDs() int {
|
||||
log.Warnf("cannot determine number of file descriptors on %s", runtime.GOOS)
|
||||
return 0
|
||||
}
|
||||
16
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/sys_unix.go
generated
vendored
Normal file
16
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/sys_unix.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getNumFDs() int {
|
||||
var l unix.Rlimit
|
||||
if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &l); err != nil {
|
||||
log.Errorw("failed to get fd limit", "error", err)
|
||||
return 0
|
||||
}
|
||||
return int(l.Cur)
|
||||
}
|
||||
11
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/sys_windows.go
generated
vendored
Normal file
11
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/sys_windows.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build windows
|
||||
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
func getNumFDs() int {
|
||||
return math.MaxInt
|
||||
}
|
||||
698
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/trace.go
generated
vendored
Normal file
698
vendor/github.com/libp2p/go-libp2p/p2p/host/resource-manager/trace.go
generated
vendored
Normal file
@@ -0,0 +1,698 @@
|
||||
package rcmgr
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
type trace struct {
|
||||
path string
|
||||
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
wg sync.WaitGroup
|
||||
|
||||
mx sync.Mutex
|
||||
done bool
|
||||
pendingWrites []interface{}
|
||||
reporters []TraceReporter
|
||||
}
|
||||
|
||||
type TraceReporter interface {
|
||||
// ConsumeEvent consumes a trace event. This is called synchronously,
|
||||
// implementations should process the event quickly.
|
||||
ConsumeEvent(TraceEvt)
|
||||
}
|
||||
|
||||
func WithTrace(path string) Option {
|
||||
return func(r *resourceManager) error {
|
||||
if r.trace == nil {
|
||||
r.trace = &trace{path: path}
|
||||
} else {
|
||||
r.trace.path = path
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithTraceReporter(reporter TraceReporter) Option {
|
||||
return func(r *resourceManager) error {
|
||||
if r.trace == nil {
|
||||
r.trace = &trace{}
|
||||
}
|
||||
r.trace.reporters = append(r.trace.reporters, reporter)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type TraceEvtTyp string
|
||||
|
||||
const (
|
||||
TraceStartEvt TraceEvtTyp = "start"
|
||||
TraceCreateScopeEvt TraceEvtTyp = "create_scope"
|
||||
TraceDestroyScopeEvt TraceEvtTyp = "destroy_scope"
|
||||
TraceReserveMemoryEvt TraceEvtTyp = "reserve_memory"
|
||||
TraceBlockReserveMemoryEvt TraceEvtTyp = "block_reserve_memory"
|
||||
TraceReleaseMemoryEvt TraceEvtTyp = "release_memory"
|
||||
TraceAddStreamEvt TraceEvtTyp = "add_stream"
|
||||
TraceBlockAddStreamEvt TraceEvtTyp = "block_add_stream"
|
||||
TraceRemoveStreamEvt TraceEvtTyp = "remove_stream"
|
||||
TraceAddConnEvt TraceEvtTyp = "add_conn"
|
||||
TraceBlockAddConnEvt TraceEvtTyp = "block_add_conn"
|
||||
TraceRemoveConnEvt TraceEvtTyp = "remove_conn"
|
||||
)
|
||||
|
||||
type scopeClass struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (s scopeClass) MarshalJSON() ([]byte, error) {
|
||||
name := s.name
|
||||
var span string
|
||||
if idx := strings.Index(name, "span:"); idx > -1 {
|
||||
name = name[:idx-1]
|
||||
span = name[idx+5:]
|
||||
}
|
||||
// System and Transient scope
|
||||
if name == "system" || name == "transient" || name == "allowlistedSystem" || name == "allowlistedTransient" {
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: name,
|
||||
Span: span,
|
||||
})
|
||||
}
|
||||
// Connection scope
|
||||
if strings.HasPrefix(name, "conn-") {
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Conn string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: "conn",
|
||||
Conn: name[5:],
|
||||
Span: span,
|
||||
})
|
||||
}
|
||||
// Stream scope
|
||||
if strings.HasPrefix(name, "stream-") {
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Stream string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: "stream",
|
||||
Stream: name[7:],
|
||||
Span: span,
|
||||
})
|
||||
}
|
||||
// Peer scope
|
||||
if strings.HasPrefix(name, "peer:") {
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Peer string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: "peer",
|
||||
Peer: name[5:],
|
||||
Span: span,
|
||||
})
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "service:") {
|
||||
if idx := strings.Index(name, "peer:"); idx > 0 { // Peer-Service scope
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Service string
|
||||
Peer string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: "service-peer",
|
||||
Service: name[8 : idx-1],
|
||||
Peer: name[idx+5:],
|
||||
Span: span,
|
||||
})
|
||||
} else { // Service scope
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Service string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: "service",
|
||||
Service: name[8:],
|
||||
Span: span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "protocol:") {
|
||||
if idx := strings.Index(name, "peer:"); idx > -1 { // Peer-Protocol scope
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Protocol string
|
||||
Peer string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: "protocol-peer",
|
||||
Protocol: name[9 : idx-1],
|
||||
Peer: name[idx+5:],
|
||||
Span: span,
|
||||
})
|
||||
} else { // Protocol scope
|
||||
return json.Marshal(struct {
|
||||
Class string
|
||||
Protocol string
|
||||
Span string `json:",omitempty"`
|
||||
}{
|
||||
Class: "protocol",
|
||||
Protocol: name[9:],
|
||||
Span: span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unrecognized scope: %s", name)
|
||||
}
|
||||
|
||||
type TraceEvt struct {
|
||||
Time string
|
||||
Type TraceEvtTyp
|
||||
|
||||
Scope *scopeClass `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
|
||||
Limit interface{} `json:",omitempty"`
|
||||
|
||||
Priority uint8 `json:",omitempty"`
|
||||
|
||||
Delta int64 `json:",omitempty"`
|
||||
DeltaIn int `json:",omitempty"`
|
||||
DeltaOut int `json:",omitempty"`
|
||||
|
||||
Memory int64 `json:",omitempty"`
|
||||
|
||||
StreamsIn int `json:",omitempty"`
|
||||
StreamsOut int `json:",omitempty"`
|
||||
|
||||
ConnsIn int `json:",omitempty"`
|
||||
ConnsOut int `json:",omitempty"`
|
||||
|
||||
FD int `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (t *trace) push(evt TraceEvt) {
|
||||
t.mx.Lock()
|
||||
defer t.mx.Unlock()
|
||||
|
||||
if t.done {
|
||||
return
|
||||
}
|
||||
evt.Time = time.Now().Format(time.RFC3339Nano)
|
||||
if evt.Name != "" {
|
||||
evt.Scope = &scopeClass{name: evt.Name}
|
||||
}
|
||||
|
||||
for _, reporter := range t.reporters {
|
||||
reporter.ConsumeEvent(evt)
|
||||
}
|
||||
|
||||
if t.path != "" {
|
||||
t.pendingWrites = append(t.pendingWrites, evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *trace) backgroundWriter(out io.WriteCloser) {
|
||||
defer t.wg.Done()
|
||||
defer out.Close()
|
||||
|
||||
gzOut := gzip.NewWriter(out)
|
||||
defer gzOut.Close()
|
||||
|
||||
jsonOut := json.NewEncoder(gzOut)
|
||||
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
var pend []interface{}
|
||||
|
||||
getEvents := func() {
|
||||
t.mx.Lock()
|
||||
tmp := t.pendingWrites
|
||||
t.pendingWrites = pend[:0]
|
||||
pend = tmp
|
||||
t.mx.Unlock()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
getEvents()
|
||||
|
||||
if len(pend) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := t.writeEvents(pend, jsonOut); err != nil {
|
||||
log.Warnf("error writing rcmgr trace: %s", err)
|
||||
t.mx.Lock()
|
||||
t.done = true
|
||||
t.mx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if err := gzOut.Flush(); err != nil {
|
||||
log.Warnf("error flushing rcmgr trace: %s", err)
|
||||
t.mx.Lock()
|
||||
t.done = true
|
||||
t.mx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
case <-t.ctx.Done():
|
||||
getEvents()
|
||||
|
||||
if len(pend) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err := t.writeEvents(pend, jsonOut); err != nil {
|
||||
log.Warnf("error writing rcmgr trace: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := gzOut.Flush(); err != nil {
|
||||
log.Warnf("error flushing rcmgr trace: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *trace) writeEvents(pend []interface{}, jout *json.Encoder) error {
|
||||
for _, e := range pend {
|
||||
if err := jout.Encode(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *trace) Start(limits Limiter) error {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.ctx, t.cancel = context.WithCancel(context.Background())
|
||||
|
||||
if t.path != "" {
|
||||
out, err := os.OpenFile(t.path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.wg.Add(1)
|
||||
go t.backgroundWriter(out)
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceStartEvt,
|
||||
Limit: limits,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *trace) Close() error {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.mx.Lock()
|
||||
|
||||
if t.done {
|
||||
t.mx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
t.cancel()
|
||||
t.done = true
|
||||
t.mx.Unlock()
|
||||
|
||||
t.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *trace) CreateScope(scope string, limit Limit) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceCreateScopeEvt,
|
||||
Name: scope,
|
||||
Limit: limit,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) DestroyScope(scope string) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceDestroyScopeEvt,
|
||||
Name: scope,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) ReserveMemory(scope string, prio uint8, size, mem int64) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceReserveMemoryEvt,
|
||||
Name: scope,
|
||||
Priority: prio,
|
||||
Delta: size,
|
||||
Memory: mem,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) BlockReserveMemory(scope string, prio uint8, size, mem int64) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceBlockReserveMemoryEvt,
|
||||
Name: scope,
|
||||
Priority: prio,
|
||||
Delta: size,
|
||||
Memory: mem,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) ReleaseMemory(scope string, size, mem int64) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceReleaseMemoryEvt,
|
||||
Name: scope,
|
||||
Delta: -size,
|
||||
Memory: mem,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) AddStream(scope string, dir network.Direction, nstreamsIn, nstreamsOut int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var deltaIn, deltaOut int
|
||||
if dir == network.DirInbound {
|
||||
deltaIn = 1
|
||||
} else {
|
||||
deltaOut = 1
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceAddStreamEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
StreamsIn: nstreamsIn,
|
||||
StreamsOut: nstreamsOut,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) BlockAddStream(scope string, dir network.Direction, nstreamsIn, nstreamsOut int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var deltaIn, deltaOut int
|
||||
if dir == network.DirInbound {
|
||||
deltaIn = 1
|
||||
} else {
|
||||
deltaOut = 1
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceBlockAddStreamEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
StreamsIn: nstreamsIn,
|
||||
StreamsOut: nstreamsOut,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) RemoveStream(scope string, dir network.Direction, nstreamsIn, nstreamsOut int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var deltaIn, deltaOut int
|
||||
if dir == network.DirInbound {
|
||||
deltaIn = -1
|
||||
} else {
|
||||
deltaOut = -1
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceRemoveStreamEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
StreamsIn: nstreamsIn,
|
||||
StreamsOut: nstreamsOut,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) AddStreams(scope string, deltaIn, deltaOut, nstreamsIn, nstreamsOut int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if deltaIn == 0 && deltaOut == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceAddStreamEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
StreamsIn: nstreamsIn,
|
||||
StreamsOut: nstreamsOut,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) BlockAddStreams(scope string, deltaIn, deltaOut, nstreamsIn, nstreamsOut int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if deltaIn == 0 && deltaOut == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceBlockAddStreamEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
StreamsIn: nstreamsIn,
|
||||
StreamsOut: nstreamsOut,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) RemoveStreams(scope string, deltaIn, deltaOut, nstreamsIn, nstreamsOut int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if deltaIn == 0 && deltaOut == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceRemoveStreamEvt,
|
||||
Name: scope,
|
||||
DeltaIn: -deltaIn,
|
||||
DeltaOut: -deltaOut,
|
||||
StreamsIn: nstreamsIn,
|
||||
StreamsOut: nstreamsOut,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) AddConn(scope string, dir network.Direction, usefd bool, nconnsIn, nconnsOut, nfd int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var deltaIn, deltaOut, deltafd int
|
||||
if dir == network.DirInbound {
|
||||
deltaIn = 1
|
||||
} else {
|
||||
deltaOut = 1
|
||||
}
|
||||
if usefd {
|
||||
deltafd = 1
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceAddConnEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
Delta: int64(deltafd),
|
||||
ConnsIn: nconnsIn,
|
||||
ConnsOut: nconnsOut,
|
||||
FD: nfd,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) BlockAddConn(scope string, dir network.Direction, usefd bool, nconnsIn, nconnsOut, nfd int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var deltaIn, deltaOut, deltafd int
|
||||
if dir == network.DirInbound {
|
||||
deltaIn = 1
|
||||
} else {
|
||||
deltaOut = 1
|
||||
}
|
||||
if usefd {
|
||||
deltafd = 1
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceBlockAddConnEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
Delta: int64(deltafd),
|
||||
ConnsIn: nconnsIn,
|
||||
ConnsOut: nconnsOut,
|
||||
FD: nfd,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) RemoveConn(scope string, dir network.Direction, usefd bool, nconnsIn, nconnsOut, nfd int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var deltaIn, deltaOut, deltafd int
|
||||
if dir == network.DirInbound {
|
||||
deltaIn = -1
|
||||
} else {
|
||||
deltaOut = -1
|
||||
}
|
||||
if usefd {
|
||||
deltafd = -1
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceRemoveConnEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
Delta: int64(deltafd),
|
||||
ConnsIn: nconnsIn,
|
||||
ConnsOut: nconnsOut,
|
||||
FD: nfd,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) AddConns(scope string, deltaIn, deltaOut, deltafd, nconnsIn, nconnsOut, nfd int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if deltaIn == 0 && deltaOut == 0 && deltafd == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceAddConnEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
Delta: int64(deltafd),
|
||||
ConnsIn: nconnsIn,
|
||||
ConnsOut: nconnsOut,
|
||||
FD: nfd,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) BlockAddConns(scope string, deltaIn, deltaOut, deltafd, nconnsIn, nconnsOut, nfd int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if deltaIn == 0 && deltaOut == 0 && deltafd == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceBlockAddConnEvt,
|
||||
Name: scope,
|
||||
DeltaIn: deltaIn,
|
||||
DeltaOut: deltaOut,
|
||||
Delta: int64(deltafd),
|
||||
ConnsIn: nconnsIn,
|
||||
ConnsOut: nconnsOut,
|
||||
FD: nfd,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *trace) RemoveConns(scope string, deltaIn, deltaOut, deltafd, nconnsIn, nconnsOut, nfd int) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if deltaIn == 0 && deltaOut == 0 && deltafd == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.push(TraceEvt{
|
||||
Type: TraceRemoveConnEvt,
|
||||
Name: scope,
|
||||
DeltaIn: -deltaIn,
|
||||
DeltaOut: -deltaOut,
|
||||
Delta: -int64(deltafd),
|
||||
ConnsIn: nconnsIn,
|
||||
ConnsOut: nconnsOut,
|
||||
FD: nfd,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user