brawny 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brawny/__init__.py +106 -0
- brawny/_context.py +232 -0
- brawny/_rpc/__init__.py +38 -0
- brawny/_rpc/broadcast.py +172 -0
- brawny/_rpc/clients.py +98 -0
- brawny/_rpc/context.py +49 -0
- brawny/_rpc/errors.py +252 -0
- brawny/_rpc/gas.py +158 -0
- brawny/_rpc/manager.py +982 -0
- brawny/_rpc/selector.py +156 -0
- brawny/accounts.py +534 -0
- brawny/alerts/__init__.py +132 -0
- brawny/alerts/abi_resolver.py +530 -0
- brawny/alerts/base.py +152 -0
- brawny/alerts/context.py +271 -0
- brawny/alerts/contracts.py +635 -0
- brawny/alerts/encoded_call.py +201 -0
- brawny/alerts/errors.py +267 -0
- brawny/alerts/events.py +680 -0
- brawny/alerts/function_caller.py +364 -0
- brawny/alerts/health.py +185 -0
- brawny/alerts/routing.py +118 -0
- brawny/alerts/send.py +364 -0
- brawny/api.py +660 -0
- brawny/chain.py +93 -0
- brawny/cli/__init__.py +16 -0
- brawny/cli/app.py +17 -0
- brawny/cli/bootstrap.py +37 -0
- brawny/cli/commands/__init__.py +41 -0
- brawny/cli/commands/abi.py +93 -0
- brawny/cli/commands/accounts.py +632 -0
- brawny/cli/commands/console.py +495 -0
- brawny/cli/commands/contract.py +139 -0
- brawny/cli/commands/health.py +112 -0
- brawny/cli/commands/init_project.py +86 -0
- brawny/cli/commands/intents.py +130 -0
- brawny/cli/commands/job_dev.py +254 -0
- brawny/cli/commands/jobs.py +308 -0
- brawny/cli/commands/logs.py +87 -0
- brawny/cli/commands/maintenance.py +182 -0
- brawny/cli/commands/migrate.py +51 -0
- brawny/cli/commands/networks.py +253 -0
- brawny/cli/commands/run.py +249 -0
- brawny/cli/commands/script.py +209 -0
- brawny/cli/commands/signer.py +248 -0
- brawny/cli/helpers.py +265 -0
- brawny/cli_templates.py +1445 -0
- brawny/config/__init__.py +74 -0
- brawny/config/models.py +404 -0
- brawny/config/parser.py +633 -0
- brawny/config/routing.py +55 -0
- brawny/config/validation.py +246 -0
- brawny/daemon/__init__.py +14 -0
- brawny/daemon/context.py +69 -0
- brawny/daemon/core.py +702 -0
- brawny/daemon/loops.py +327 -0
- brawny/db/__init__.py +78 -0
- brawny/db/base.py +986 -0
- brawny/db/base_new.py +165 -0
- brawny/db/circuit_breaker.py +97 -0
- brawny/db/global_cache.py +298 -0
- brawny/db/mappers.py +182 -0
- brawny/db/migrate.py +349 -0
- brawny/db/migrations/001_init.sql +186 -0
- brawny/db/migrations/002_add_included_block.sql +7 -0
- brawny/db/migrations/003_add_broadcast_at.sql +10 -0
- brawny/db/migrations/004_broadcast_binding.sql +20 -0
- brawny/db/migrations/005_add_retry_after.sql +9 -0
- brawny/db/migrations/006_add_retry_count_column.sql +11 -0
- brawny/db/migrations/007_add_gap_tracking.sql +18 -0
- brawny/db/migrations/008_add_transactions.sql +72 -0
- brawny/db/migrations/009_add_intent_metadata.sql +5 -0
- brawny/db/migrations/010_add_nonce_gap_index.sql +9 -0
- brawny/db/migrations/011_add_job_logs.sql +24 -0
- brawny/db/migrations/012_add_claimed_by.sql +5 -0
- brawny/db/ops/__init__.py +29 -0
- brawny/db/ops/attempts.py +108 -0
- brawny/db/ops/blocks.py +83 -0
- brawny/db/ops/cache.py +93 -0
- brawny/db/ops/intents.py +296 -0
- brawny/db/ops/jobs.py +110 -0
- brawny/db/ops/logs.py +97 -0
- brawny/db/ops/nonces.py +322 -0
- brawny/db/postgres.py +2535 -0
- brawny/db/postgres_new.py +196 -0
- brawny/db/queries.py +584 -0
- brawny/db/sqlite.py +2733 -0
- brawny/db/sqlite_new.py +191 -0
- brawny/history.py +126 -0
- brawny/interfaces.py +136 -0
- brawny/invariants.py +155 -0
- brawny/jobs/__init__.py +26 -0
- brawny/jobs/base.py +287 -0
- brawny/jobs/discovery.py +233 -0
- brawny/jobs/job_validation.py +111 -0
- brawny/jobs/kv.py +125 -0
- brawny/jobs/registry.py +283 -0
- brawny/keystore.py +484 -0
- brawny/lifecycle.py +551 -0
- brawny/logging.py +290 -0
- brawny/metrics.py +594 -0
- brawny/model/__init__.py +53 -0
- brawny/model/contexts.py +319 -0
- brawny/model/enums.py +70 -0
- brawny/model/errors.py +194 -0
- brawny/model/events.py +93 -0
- brawny/model/startup.py +20 -0
- brawny/model/types.py +483 -0
- brawny/networks/__init__.py +96 -0
- brawny/networks/config.py +269 -0
- brawny/networks/manager.py +423 -0
- brawny/obs/__init__.py +67 -0
- brawny/obs/emit.py +158 -0
- brawny/obs/health.py +175 -0
- brawny/obs/heartbeat.py +133 -0
- brawny/reconciliation.py +108 -0
- brawny/scheduler/__init__.py +19 -0
- brawny/scheduler/poller.py +472 -0
- brawny/scheduler/reorg.py +632 -0
- brawny/scheduler/runner.py +708 -0
- brawny/scheduler/shutdown.py +371 -0
- brawny/script_tx.py +297 -0
- brawny/scripting.py +251 -0
- brawny/startup.py +76 -0
- brawny/telegram.py +393 -0
- brawny/testing.py +108 -0
- brawny/tx/__init__.py +41 -0
- brawny/tx/executor.py +1071 -0
- brawny/tx/fees.py +50 -0
- brawny/tx/intent.py +423 -0
- brawny/tx/monitor.py +628 -0
- brawny/tx/nonce.py +498 -0
- brawny/tx/replacement.py +456 -0
- brawny/tx/utils.py +26 -0
- brawny/utils.py +205 -0
- brawny/validation.py +69 -0
- brawny-0.1.13.dist-info/METADATA +156 -0
- brawny-0.1.13.dist-info/RECORD +141 -0
- brawny-0.1.13.dist-info/WHEEL +5 -0
- brawny-0.1.13.dist-info/entry_points.txt +2 -0
- brawny-0.1.13.dist-info/top_level.txt +1 -0
brawny/cli_templates.py
ADDED
|
@@ -0,0 +1,1445 @@
|
|
|
1
|
+
"""Templates for brawny init command."""
|
|
2
|
+
|
|
3
|
+
PYPROJECT_TEMPLATE = """\
|
|
4
|
+
[build-system]
|
|
5
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "{project_name}"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "brawny keeper project"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = ["brawny>=0.1.0"]
|
|
14
|
+
|
|
15
|
+
[tool.setuptools.packages.find]
|
|
16
|
+
where = ["."]
|
|
17
|
+
include = ["{package_name}*"]
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
CONFIG_TEMPLATE = """\
|
|
21
|
+
# brawny configuration
|
|
22
|
+
# See: https://github.com/yearn/brawny#configuration
|
|
23
|
+
|
|
24
|
+
# Core settings
|
|
25
|
+
database_url: sqlite:///data/brawny.db
|
|
26
|
+
rpc_groups:
|
|
27
|
+
primary:
|
|
28
|
+
endpoints:
|
|
29
|
+
- ${{RPC_URL}}
|
|
30
|
+
rpc_default_group: primary
|
|
31
|
+
chain_id: 1
|
|
32
|
+
|
|
33
|
+
# Keystore configuration
|
|
34
|
+
# Options: "file" (preferred) or "env" (least preferred)
|
|
35
|
+
keystore_type: file
|
|
36
|
+
keystore_path: ~/.brawny/keys
|
|
37
|
+
|
|
38
|
+
# SQLite requires worker_count: 1. Use PostgreSQL for multi-worker setups.
|
|
39
|
+
worker_count: 1
|
|
40
|
+
|
|
41
|
+
# Prometheus metrics port (default: 9091)
|
|
42
|
+
# metrics_port: 9091
|
|
43
|
+
|
|
44
|
+
# Telegram alerts (optional)
|
|
45
|
+
# telegram:
|
|
46
|
+
# bot_token: ${{TELEGRAM_BOT_TOKEN}}
|
|
47
|
+
# chats:
|
|
48
|
+
# ops: "-1001234567890"
|
|
49
|
+
# default: ["ops"]
|
|
50
|
+
# parse_mode: "Markdown"
|
|
51
|
+
|
|
52
|
+
# Advanced settings (optional)
|
|
53
|
+
# advanced:
|
|
54
|
+
# poll_interval_seconds: 1.0
|
|
55
|
+
# reorg_depth: 32
|
|
56
|
+
# default_deadline_seconds: 3600
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
ENV_EXAMPLE_TEMPLATE = """\
|
|
60
|
+
# RPC endpoint (required)
|
|
61
|
+
RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
|
|
62
|
+
|
|
63
|
+
# Keystore password (file keystore mode)
|
|
64
|
+
# BRAWNY_KEYSTORE_PASSWORD_WORKER=your-password
|
|
65
|
+
# Then import the key:
|
|
66
|
+
# brawny accounts import --name worker --private-key 0x...
|
|
67
|
+
|
|
68
|
+
# Telegram alerts (optional)
|
|
69
|
+
# TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
GITIGNORE_TEMPLATE = """\
|
|
73
|
+
# Python
|
|
74
|
+
__pycache__/
|
|
75
|
+
*.py[cod]
|
|
76
|
+
*$py.class
|
|
77
|
+
*.so
|
|
78
|
+
.Python
|
|
79
|
+
build/
|
|
80
|
+
develop-eggs/
|
|
81
|
+
dist/
|
|
82
|
+
downloads/
|
|
83
|
+
eggs/
|
|
84
|
+
.eggs/
|
|
85
|
+
lib/
|
|
86
|
+
lib64/
|
|
87
|
+
parts/
|
|
88
|
+
sdist/
|
|
89
|
+
var/
|
|
90
|
+
wheels/
|
|
91
|
+
*.egg-info/
|
|
92
|
+
.installed.cfg
|
|
93
|
+
*.egg
|
|
94
|
+
|
|
95
|
+
# Virtual environments
|
|
96
|
+
.env
|
|
97
|
+
.venv
|
|
98
|
+
env/
|
|
99
|
+
venv/
|
|
100
|
+
ENV/
|
|
101
|
+
|
|
102
|
+
# brawny
|
|
103
|
+
data/
|
|
104
|
+
*.db
|
|
105
|
+
*.db-journal
|
|
106
|
+
|
|
107
|
+
# IDE
|
|
108
|
+
.idea/
|
|
109
|
+
.vscode/
|
|
110
|
+
*.swp
|
|
111
|
+
*.swo
|
|
112
|
+
*~
|
|
113
|
+
|
|
114
|
+
# OS
|
|
115
|
+
.DS_Store
|
|
116
|
+
Thumbs.db
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
INIT_JOBS_TEMPLATE = '"""Job definitions - auto-discovered from ./jobs."""\n'
|
|
120
|
+
|
|
121
|
+
AGENTS_TEMPLATE = """
|
|
122
|
+
# Agent Guide: Build a Compliant brawny Job
|
|
123
|
+
|
|
124
|
+
This file is meant for user agents that generate new job files. It is a fast, practical spec.
|
|
125
|
+
|
|
126
|
+
## Golden Rules
|
|
127
|
+
- Avoid over-engineering.
|
|
128
|
+
- Aim for simplicity and elegance.
|
|
129
|
+
|
|
130
|
+
## Job File Checklist (Minimal)
|
|
131
|
+
- Location: `jobs/<job_name>.py`
|
|
132
|
+
- Import `Job` and `job`.
|
|
133
|
+
- Add `@job` decorator (omit it to hide a WIP job from discovery/validation).
|
|
134
|
+
- Implement `check()` (sync or async).
|
|
135
|
+
- If it sends a transaction, implement `build_intent()` (sync).
|
|
136
|
+
|
|
137
|
+
## Required vs Optional Hooks
|
|
138
|
+
|
|
139
|
+
### Required
|
|
140
|
+
- `check(self) -> Trigger | None` OR `check(self, ctx) -> Trigger | None`
|
|
141
|
+
- Must return `trigger(...)` or `None`.
|
|
142
|
+
- **Implicit style** `def check(self):` - use API helpers (`block`, `kv`, `Contract`, `ctx()`)
|
|
143
|
+
- **Explicit style** `def check(self, ctx):` - ctx passed directly (param MUST be named 'ctx')
|
|
144
|
+
- Can be async: `async def check(self)` or `async def check(self, ctx)`
|
|
145
|
+
|
|
146
|
+
### Required only for tx jobs
|
|
147
|
+
- `build_intent(self, trigger) -> TxIntentSpec`
|
|
148
|
+
- Build calldata and return `intent(...)`.
|
|
149
|
+
- Only called if `trigger.tx_required` is True.
|
|
150
|
+
|
|
151
|
+
### Optional simulation hook
|
|
152
|
+
- `validate_simulation(self, output) -> bool`
|
|
153
|
+
- Return False to fail the intent after a successful simulation.
|
|
154
|
+
|
|
155
|
+
### Optional alert hooks
|
|
156
|
+
- `alert_triggered(self, ctx)` - Called when job triggers
|
|
157
|
+
- `alert_confirmed(self, ctx)` - Called after TX confirms (ctx.receipt available)
|
|
158
|
+
- `alert_failed(self, ctx)` - Called on failure (ctx.tx can be None for pre-broadcast failures)
|
|
159
|
+
- Return `str`, `(str, parse_mode)`, or `None`.
|
|
160
|
+
|
|
161
|
+
### Optional lifecycle hooks
|
|
162
|
+
- `on_success(self, ctx, receipt, intent, attempt)`
|
|
163
|
+
- `on_failure(self, ctx, error, intent, attempt)` # attempt can be None pre-broadcast
|
|
164
|
+
|
|
165
|
+
## Job Class Attributes
|
|
166
|
+
|
|
167
|
+
### Required (auto-derived if not set and @job is used)
|
|
168
|
+
- `job_id: str` - Stable identifier (must not change)
|
|
169
|
+
- `name: str` - Human-readable name for logs/alerts
|
|
170
|
+
|
|
171
|
+
### Optional scheduling
|
|
172
|
+
- `check_interval_blocks: int = 1` - Min blocks between check() calls
|
|
173
|
+
- `check_timeout_seconds: int = 30` - Timeout for check()
|
|
174
|
+
- `build_timeout_seconds: int = 10` - Timeout for build_intent()
|
|
175
|
+
- `max_in_flight_intents: int | None = None` - Cap on active intents
|
|
176
|
+
|
|
177
|
+
### Optional gas overrides (all values in wei)
|
|
178
|
+
- `max_fee: int | None = None` - Max fee cap for gating/txs (None = no gating)
|
|
179
|
+
- `priority_fee: int | None = None` - Tip override for this job
|
|
180
|
+
|
|
181
|
+
### Optional simulation
|
|
182
|
+
- `disable_simulation: bool = False` - Skip pre-broadcast simulation
|
|
183
|
+
- `rpc: str | None = None` - Override RPC for simulation
|
|
184
|
+
|
|
185
|
+
### Broadcast routing (via @job decorator)
|
|
186
|
+
Configure broadcast routing using the `@job` decorator:
|
|
187
|
+
```python
|
|
188
|
+
@job(job_id="arb_exec", rpc_group="flashbots", signer="hot1")
|
|
189
|
+
class ArbitrageExecutor(Job):
|
|
190
|
+
...
|
|
191
|
+
```
|
|
192
|
+
- `job_id` - Optional override (defaults to snake_case of class name)
|
|
193
|
+
- `rpc_group` - Name of RPC group for reads and broadcasts
|
|
194
|
+
- `broadcast_group` - Name of RPC group for broadcasts (default: uses rpc_default_group)
|
|
195
|
+
- `read_group` - Name of RPC group for read operations (default: uses rpc_default_group)
|
|
196
|
+
- `signer` - Name of signer alias (required for tx jobs)
|
|
197
|
+
|
|
198
|
+
Define RPC groups in config:
|
|
199
|
+
```yaml
|
|
200
|
+
rpc_groups:
|
|
201
|
+
primary:
|
|
202
|
+
endpoints:
|
|
203
|
+
- https://eth.llamarpc.com
|
|
204
|
+
private:
|
|
205
|
+
endpoints:
|
|
206
|
+
- https://rpc.flashbots.net
|
|
207
|
+
- https://relay.flashbots.net
|
|
208
|
+
rpc_default_group: primary
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Alert routing
|
|
212
|
+
- `@job(alert_to="ops")` - Route alerts to named chat defined in config
|
|
213
|
+
- `@job(alert_to=["ops", "dev"])` - Route to multiple chats
|
|
214
|
+
- Names must be defined in `telegram.chats` config section
|
|
215
|
+
- If not specified, uses `telegram.default` from config
|
|
216
|
+
|
|
217
|
+
## Core API (What to Use)
|
|
218
|
+
|
|
219
|
+
### Contract access (brownie-style)
|
|
220
|
+
```python
|
|
221
|
+
from brawny import Contract
|
|
222
|
+
vault = Contract(self.vault_address) # By address
|
|
223
|
+
decimals = vault.decimals() # View call
|
|
224
|
+
data = vault.harvest.encode_input() # Get calldata
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
### JSON interfaces (brownie-style)
|
|
229
|
+
Place ABI JSON files in `./interfaces`, then:
|
|
230
|
+
```python
|
|
231
|
+
from brawny import interface
|
|
232
|
+
token = interface.IERC20("0x1234...")
|
|
233
|
+
balance = token.balanceOf("0xabc...")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Job hook helpers (implicit context)
|
|
237
|
+
```python
|
|
238
|
+
from brawny import trigger, intent, block, gas_ok
|
|
239
|
+
return trigger(reason="...", data={...}, idempotency_parts=[block.number])
|
|
240
|
+
return intent(signer_address="worker", to_address=addr, data=calldata)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Event access in alert hooks (brownie-compatible)
|
|
244
|
+
```python
|
|
245
|
+
def alert_confirmed(self, ctx):
|
|
246
|
+
deposit = ctx.events["Deposit"][0] # First Deposit event
|
|
247
|
+
amount = deposit["amount"] # Field access
|
|
248
|
+
if "Deposit" in ctx.events: # Check if event exists
|
|
249
|
+
...
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Other context access
|
|
253
|
+
- `ctx()` - Get full CheckContext/BuildContext when using implicit style
|
|
254
|
+
- `block.number`, `block.timestamp` - Current block info
|
|
255
|
+
- `rpc.*` - RPC manager proxy (e.g., `rpc.get_gas_price()`)
|
|
256
|
+
- `gas_ok()` - Check if current gas is below job's max_fee (async)
|
|
257
|
+
- `gas_quote()` - Get current base_fee (async)
|
|
258
|
+
- `kv.get(key, default=None)`, `kv.set(key, value)` - Persistent KV store (import from brawny)
|
|
259
|
+
|
|
260
|
+
### Accounts
|
|
261
|
+
- Use `intent(signer_address=...)` with a signer alias or address.
|
|
262
|
+
- If you set `@job(signer="alias")`, use `self.signer` (alias) or `self.signer_address` (resolved address).
|
|
263
|
+
- The signer alias must exist in the accounts directory (`~/.brawny/accounts`).
|
|
264
|
+
|
|
265
|
+
## Example: Transaction Job
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from brawny import Job, job, Contract, trigger, intent, block
|
|
269
|
+
|
|
270
|
+
@job(signer="worker")
|
|
271
|
+
class MyKeeperJob(Job):
|
|
272
|
+
job_id = "my_keeper"
|
|
273
|
+
name = "My Keeper"
|
|
274
|
+
check_interval_blocks = 1
|
|
275
|
+
keeper_address = "0x..."
|
|
276
|
+
|
|
277
|
+
def check(self, ctx):
|
|
278
|
+
keeper = Contract(self.keeper_address)
|
|
279
|
+
if keeper.canWork():
|
|
280
|
+
return trigger(
|
|
281
|
+
reason="Keeper can work",
|
|
282
|
+
idempotency_parts=[block.number],
|
|
283
|
+
)
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
def build_intent(self, trig):
|
|
287
|
+
keeper = Contract(self.keeper_address)
|
|
288
|
+
return intent(
|
|
289
|
+
signer_address=self.signer,
|
|
290
|
+
to_address=self.keeper_address,
|
|
291
|
+
data=keeper.work.encode_input(),
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Example: Job with Custom Broadcast and Alerts
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from brawny import Job, Contract, trigger, intent, explorer_link
|
|
299
|
+
from brawny.jobs.registry import job
|
|
300
|
+
|
|
301
|
+
@job(rpc_group="flashbots", signer="treasury-signer", alert_to="private_ops")
|
|
302
|
+
class TreasuryJob(Job):
|
|
303
|
+
\"\"\"Critical treasury operations with dedicated RPC and private alerts.\"\"\"
|
|
304
|
+
|
|
305
|
+
name = "Treasury Operations"
|
|
306
|
+
check_interval_blocks = 1
|
|
307
|
+
treasury_address = "0x..."
|
|
308
|
+
|
|
309
|
+
def check(self, ctx):
|
|
310
|
+
treasury = Contract(self.treasury_address)
|
|
311
|
+
if treasury.needsRebalance():
|
|
312
|
+
return trigger(reason="Treasury needs rebalancing")
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
def build_intent(self, trig):
|
|
316
|
+
treasury = Contract(self.treasury_address)
|
|
317
|
+
return intent(
|
|
318
|
+
signer_address=self.signer,
|
|
319
|
+
to_address=self.treasury_address,
|
|
320
|
+
data=treasury.rebalance.encode_input(),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def alert_confirmed(self, ctx):
|
|
324
|
+
if not ctx.tx:
|
|
325
|
+
return None
|
|
326
|
+
return f"Treasury rebalanced: {explorer_link(ctx.tx.hash)}"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Example: Monitor-Only Job (Implicit Context Style)
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
from brawny import Job, job, Contract, trigger, kv
|
|
333
|
+
|
|
334
|
+
@job
|
|
335
|
+
class MonitorJob(Job):
|
|
336
|
+
job_id = "monitor"
|
|
337
|
+
name = "Monitor"
|
|
338
|
+
|
|
339
|
+
def check(self): # No ctx param - uses implicit context
|
|
340
|
+
value = Contract("0x...").value()
|
|
341
|
+
last = kv.get("last", 0)
|
|
342
|
+
if value > last:
|
|
343
|
+
kv.set("last", value)
|
|
344
|
+
return trigger(
|
|
345
|
+
reason="Value increased",
|
|
346
|
+
data={"value": value},
|
|
347
|
+
tx_required=False,
|
|
348
|
+
)
|
|
349
|
+
return None
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Natural-Language -> Job Translation Guide
|
|
353
|
+
|
|
354
|
+
When a user says:
|
|
355
|
+
- **"Check X every block"** -> `check_interval_blocks = 1`
|
|
356
|
+
- **"Only run if gas below Y"** -> set `max_fee` (wei) and use `await gas_ok()` in async check()
|
|
357
|
+
- **"Use signer Z"** -> `@job(signer="Z")` and use `self.signer` in `intent(...)`
|
|
358
|
+
- **"Alert on success/failure"** -> implement `alert_confirmed` / `alert_failed`
|
|
359
|
+
- **"Remember last value"** -> use `kv.get/set` (import from brawny)
|
|
360
|
+
- **"Use Flashbots"** -> `@job(rpc_group="flashbots")` with flashbots group in config
|
|
361
|
+
- **"Send alerts to private channel"** -> `@job(alert_to="private_ops")` with chat in config
|
|
362
|
+
|
|
363
|
+
## Failure Modes
|
|
364
|
+
|
|
365
|
+
The `alert_failed` hook provides rich context about what failed and when.
|
|
366
|
+
|
|
367
|
+
### Failure Classification
|
|
368
|
+
|
|
369
|
+
**FailureType** (what failed):
|
|
370
|
+
- `SIMULATION_REVERTED` - TX would revert on-chain (permanent)
|
|
371
|
+
- `SIMULATION_NETWORK_ERROR` - RPC error during simulation (transient)
|
|
372
|
+
- `DEADLINE_EXPIRED` - Intent took too long (permanent)
|
|
373
|
+
- `SIGNER_FAILED` - Keystore/signer issue
|
|
374
|
+
- `NONCE_FAILED` - Couldn't reserve nonce
|
|
375
|
+
- `SIGN_FAILED` - Signing error
|
|
376
|
+
- `BROADCAST_FAILED` - RPC rejected transaction (transient)
|
|
377
|
+
- `TX_REVERTED` - On-chain revert (permanent)
|
|
378
|
+
- `NONCE_CONSUMED` - Nonce used by another transaction
|
|
379
|
+
- `CHECK_EXCEPTION` - job.check() raised an exception
|
|
380
|
+
- `BUILD_TX_EXCEPTION` - job.build_tx() raised an exception
|
|
381
|
+
- `UNKNOWN` - Fallback for unexpected failures
|
|
382
|
+
|
|
383
|
+
**FailureStage** (when it failed):
|
|
384
|
+
- `PRE_BROADCAST` - Failed before reaching the chain
|
|
385
|
+
- `BROADCAST` - Failed during broadcast
|
|
386
|
+
- `POST_BROADCAST` - Failed after broadcast (on-chain)
|
|
387
|
+
|
|
388
|
+
### AlertContext in alert_failed
|
|
389
|
+
|
|
390
|
+
```python
|
|
391
|
+
# AlertContext fields (all hooks)
|
|
392
|
+
ctx.job # JobMetadata (id, name)
|
|
393
|
+
ctx.trigger # Trigger that initiated this flow
|
|
394
|
+
ctx.chain_id # Chain ID
|
|
395
|
+
ctx.hook # HookType enum (TRIGGERED, CONFIRMED, FAILED)
|
|
396
|
+
ctx.tx # TxInfo | None (hash, nonce, gas params)
|
|
397
|
+
ctx.receipt # TxReceipt | None (only in alert_confirmed)
|
|
398
|
+
ctx.block # BlockInfo | None
|
|
399
|
+
ctx.error_info # ErrorInfo | None (structured, JSON-safe)
|
|
400
|
+
ctx.failure_type # FailureType | None
|
|
401
|
+
ctx.failure_stage # FailureStage | None
|
|
402
|
+
ctx.events # EventDict (only in alert_confirmed)
|
|
403
|
+
|
|
404
|
+
# AlertContext properties
|
|
405
|
+
ctx.is_permanent_failure # True if retrying won't help
|
|
406
|
+
ctx.is_transient_failure # True if failure might resolve on retry
|
|
407
|
+
ctx.error_message # Convenience: error_info.message or "unknown"
|
|
408
|
+
|
|
409
|
+
# AlertContext methods
|
|
410
|
+
ctx.explorer_link(hash) # "[🔗 View](url)" markdown link
|
|
411
|
+
ctx.shorten(hex_str) # "0x1234...abcd"
|
|
412
|
+
ctx.has_receipt() # True if receipt available
|
|
413
|
+
ctx.has_error() # True if error_info available
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Example: Handling Failures
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from brawny import Job, job
|
|
420
|
+
from brawny.model.errors import FailureType
|
|
421
|
+
|
|
422
|
+
@job
|
|
423
|
+
class RobustJob(Job):
|
|
424
|
+
job_id = "robust_job"
|
|
425
|
+
name = "Robust Job"
|
|
426
|
+
|
|
427
|
+
def alert_failed(self, ctx):
|
|
428
|
+
# Suppress alerts for transient failures
|
|
429
|
+
if ctx.is_transient_failure:
|
|
430
|
+
return None # No alert
|
|
431
|
+
|
|
432
|
+
# Detailed message for permanent failures
|
|
433
|
+
if ctx.failure_type == FailureType.SIMULATION_REVERTED:
|
|
434
|
+
return f"TX would revert: {ctx.error_message}"
|
|
435
|
+
elif ctx.failure_type == FailureType.TX_REVERTED:
|
|
436
|
+
if not ctx.tx:
|
|
437
|
+
return f"TX reverted on-chain: {ctx.error_message}"
|
|
438
|
+
return f"TX reverted on-chain: {ctx.explorer_link(ctx.tx.hash)}"
|
|
439
|
+
elif ctx.failure_type == FailureType.NONCE_CONSUMED:
|
|
440
|
+
return "Nonce conflict! Check signer activity."
|
|
441
|
+
elif ctx.failure_type == FailureType.CHECK_EXCEPTION:
|
|
442
|
+
return f"check() crashed: {ctx.error_message}"
|
|
443
|
+
elif ctx.failure_type == FailureType.BUILD_TX_EXCEPTION:
|
|
444
|
+
return f"build_intent() crashed: {ctx.error_message}"
|
|
445
|
+
else:
|
|
446
|
+
return f"Failed ({ctx.failure_type.value}): {ctx.error_message}"
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Required Output from Agent
|
|
450
|
+
When generating a new job file, the agent must provide:
|
|
451
|
+
- File path
|
|
452
|
+
- Job class name
|
|
453
|
+
- `job_id` and `name`
|
|
454
|
+
- `check()` implementation
|
|
455
|
+
- `build_intent()` if tx required
|
|
456
|
+
- Any alert hooks requested
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
EXAMPLES_TEMPLATE = '''\
|
|
460
|
+
"""Example job patterns - NOT registered.
|
|
461
|
+
|
|
462
|
+
These are reference implementations. To use them:
|
|
463
|
+
1. Copy the class to a new file (e.g., my_job.py)
|
|
464
|
+
2. Add @job decorator
|
|
465
|
+
3. Customize the implementation
|
|
466
|
+
|
|
467
|
+
Delete this file when you no longer need it.
|
|
468
|
+
"""
|
|
469
|
+
from brawny import Job, Contract, trigger, kv
|
|
470
|
+
|
|
471
|
+
# Note: No @job decorator - these are templates only
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class MonitorOnlyJob(Job):
|
|
475
|
+
"""Monitor-only job - alerts without transactions.
|
|
476
|
+
|
|
477
|
+
Use cases:
|
|
478
|
+
- Price deviation alerts
|
|
479
|
+
- Health check monitoring
|
|
480
|
+
- Threshold breach notifications
|
|
481
|
+
|
|
482
|
+
Outcome:
|
|
483
|
+
- Creates: Trigger only (no intent, no transaction)
|
|
484
|
+
- Alerts: alert_triggered only
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
job_id = "monitor_example"
|
|
488
|
+
name = "Monitor Example"
|
|
489
|
+
check_interval_blocks = 10
|
|
490
|
+
|
|
491
|
+
def __init__(self, oracle_address: str, threshold_percent: float = 5.0):
|
|
492
|
+
self.oracle_address = oracle_address
|
|
493
|
+
self.threshold_percent = threshold_percent
|
|
494
|
+
|
|
495
|
+
def check(self, ctx):
|
|
496
|
+
"""Check if condition is met.
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
Trigger with tx_required=False, or None
|
|
500
|
+
"""
|
|
501
|
+
oracle = Contract(self.oracle_address)
|
|
502
|
+
price = oracle.latestAnswer() / 1e8
|
|
503
|
+
|
|
504
|
+
last_price = kv.get("last_price")
|
|
505
|
+
if last_price is not None:
|
|
506
|
+
change_pct = abs(price - last_price) / last_price * 100
|
|
507
|
+
if change_pct >= self.threshold_percent:
|
|
508
|
+
kv.set("last_price", price)
|
|
509
|
+
return trigger(
|
|
510
|
+
reason=f"Price changed {change_pct:.2f}%",
|
|
511
|
+
data={
|
|
512
|
+
"old_price": last_price,
|
|
513
|
+
"new_price": price,
|
|
514
|
+
"change_percent": change_pct,
|
|
515
|
+
},
|
|
516
|
+
tx_required=False, # No transaction needed
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
kv.set("last_price", price)
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
def alert_triggered(self, ctx):
|
|
523
|
+
"""Format alert message.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Tuple of (message, parse_mode) or string
|
|
527
|
+
"""
|
|
528
|
+
data = {}
|
|
529
|
+
return (
|
|
530
|
+
f"Price alert: {data['old_price']:.2f} -> {data['new_price']:.2f}\\n"
|
|
531
|
+
f"Change: {data['change_percent']:.2f}%",
|
|
532
|
+
"Markdown",
|
|
533
|
+
)
|
|
534
|
+
'''
|
|
535
|
+
|
|
536
|
+
# Monitoring stack templates
|
|
537
|
+
DOCKER_COMPOSE_MONITORING_TEMPLATE = """\
|
|
538
|
+
# Production-friendly Prometheus + Grafana stack for Brawny
|
|
539
|
+
# Usage: docker-compose -f monitoring/docker-compose.yml up -d
|
|
540
|
+
#
|
|
541
|
+
# Access:
|
|
542
|
+
# Prometheus: http://localhost:9090
|
|
543
|
+
# Grafana: http://localhost:3000 (admin / admin)
|
|
544
|
+
#
|
|
545
|
+
# For production, set GF_ADMIN_PASSWORD in environment or .env
|
|
546
|
+
|
|
547
|
+
services:
|
|
548
|
+
prometheus:
|
|
549
|
+
image: prom/prometheus:v2.48.0
|
|
550
|
+
container_name: brawny-prometheus
|
|
551
|
+
ports:
|
|
552
|
+
- "9090:9090"
|
|
553
|
+
volumes:
|
|
554
|
+
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
|
555
|
+
- prometheus_data:/prometheus
|
|
556
|
+
command:
|
|
557
|
+
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
558
|
+
- '--storage.tsdb.path=/prometheus'
|
|
559
|
+
- '--storage.tsdb.retention.time=30d'
|
|
560
|
+
extra_hosts:
|
|
561
|
+
- "host.docker.internal:host-gateway"
|
|
562
|
+
healthcheck:
|
|
563
|
+
test: ["CMD", "wget", "-qO-", "http://localhost:9090/-/ready"]
|
|
564
|
+
interval: 10s
|
|
565
|
+
timeout: 3s
|
|
566
|
+
retries: 3
|
|
567
|
+
restart: unless-stopped
|
|
568
|
+
|
|
569
|
+
grafana:
|
|
570
|
+
image: grafana/grafana:10.2.3
|
|
571
|
+
container_name: brawny-grafana
|
|
572
|
+
ports:
|
|
573
|
+
- "3000:3000"
|
|
574
|
+
volumes:
|
|
575
|
+
- grafana_data:/var/lib/grafana
|
|
576
|
+
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
|
577
|
+
environment:
|
|
578
|
+
- GF_SECURITY_ADMIN_USER=admin
|
|
579
|
+
- GF_SECURITY_ADMIN_PASSWORD=${{GF_ADMIN_PASSWORD:-admin}}
|
|
580
|
+
- GF_USERS_ALLOW_SIGN_UP=false
|
|
581
|
+
healthcheck:
|
|
582
|
+
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
|
|
583
|
+
interval: 10s
|
|
584
|
+
timeout: 3s
|
|
585
|
+
retries: 3
|
|
586
|
+
restart: unless-stopped
|
|
587
|
+
|
|
588
|
+
volumes:
|
|
589
|
+
prometheus_data:
|
|
590
|
+
grafana_data:
|
|
591
|
+
"""
|
|
592
|
+
|
|
593
|
+
PROMETHEUS_CONFIG_TEMPLATE = """\
|
|
594
|
+
# Prometheus configuration for Brawny
|
|
595
|
+
#
|
|
596
|
+
# Default: scrapes metrics from Brawny on host at localhost:9091
|
|
597
|
+
#
|
|
598
|
+
# Troubleshooting:
|
|
599
|
+
# macOS/Windows: host.docker.internal:9091 works out of the box
|
|
600
|
+
# Linux: if target is down, replace with your host IP (e.g., 172.17.0.1:9091)
|
|
601
|
+
# or run Brawny in the same Docker network
|
|
602
|
+
|
|
603
|
+
global:
|
|
604
|
+
scrape_interval: 15s
|
|
605
|
+
evaluation_interval: 15s
|
|
606
|
+
|
|
607
|
+
scrape_configs:
|
|
608
|
+
- job_name: 'brawny'
|
|
609
|
+
static_configs:
|
|
610
|
+
- targets: ['host.docker.internal:9091']
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
GRAFANA_DATASOURCE_TEMPLATE = """\
|
|
614
|
+
# Auto-provision Prometheus datasource
|
|
615
|
+
# UID is stable so dashboards can reference it reliably
|
|
616
|
+
apiVersion: 1
|
|
617
|
+
|
|
618
|
+
datasources:
|
|
619
|
+
- name: Prometheus
|
|
620
|
+
uid: prometheus
|
|
621
|
+
type: prometheus
|
|
622
|
+
access: proxy
|
|
623
|
+
url: http://prometheus:9090
|
|
624
|
+
isDefault: true
|
|
625
|
+
editable: false
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
GRAFANA_DASHBOARDS_PROVIDER_TEMPLATE = """\
|
|
629
|
+
# Auto-provision dashboards from this directory
|
|
630
|
+
apiVersion: 1
|
|
631
|
+
|
|
632
|
+
providers:
|
|
633
|
+
- name: 'brawny'
|
|
634
|
+
orgId: 1
|
|
635
|
+
folder: ''
|
|
636
|
+
type: file
|
|
637
|
+
disableDeletion: false
|
|
638
|
+
editable: true
|
|
639
|
+
options:
|
|
640
|
+
path: /etc/grafana/provisioning/dashboards
|
|
641
|
+
"""
|
|
642
|
+
|
|
643
|
+
GRAFANA_DASHBOARD_TEMPLATE = """\
|
|
644
|
+
{
|
|
645
|
+
"annotations": {
|
|
646
|
+
"list": []
|
|
647
|
+
},
|
|
648
|
+
"editable": true,
|
|
649
|
+
"fiscalYearStartMonth": 0,
|
|
650
|
+
"graphTooltip": 0,
|
|
651
|
+
"id": null,
|
|
652
|
+
"links": [],
|
|
653
|
+
"panels": [
|
|
654
|
+
{
|
|
655
|
+
"gridPos": {
|
|
656
|
+
"h": 1,
|
|
657
|
+
"w": 24,
|
|
658
|
+
"x": 0,
|
|
659
|
+
"y": 0
|
|
660
|
+
},
|
|
661
|
+
"id": 1,
|
|
662
|
+
"title": "Status",
|
|
663
|
+
"type": "row"
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
"datasource": {
|
|
667
|
+
"type": "prometheus",
|
|
668
|
+
"uid": "prometheus"
|
|
669
|
+
},
|
|
670
|
+
"fieldConfig": {
|
|
671
|
+
"defaults": {
|
|
672
|
+
"color": {
|
|
673
|
+
"mode": "thresholds"
|
|
674
|
+
},
|
|
675
|
+
"mappings": [
|
|
676
|
+
{
|
|
677
|
+
"type": "value",
|
|
678
|
+
"options": {
|
|
679
|
+
"0": {
|
|
680
|
+
"text": "false"
|
|
681
|
+
},
|
|
682
|
+
"1": {
|
|
683
|
+
"text": "true"
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
],
|
|
688
|
+
"thresholds": {
|
|
689
|
+
"mode": "absolute",
|
|
690
|
+
"steps": [
|
|
691
|
+
{
|
|
692
|
+
"color": "red",
|
|
693
|
+
"value": null
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
"color": "green",
|
|
697
|
+
"value": 1
|
|
698
|
+
}
|
|
699
|
+
]
|
|
700
|
+
},
|
|
701
|
+
"unit": "short"
|
|
702
|
+
},
|
|
703
|
+
"overrides": []
|
|
704
|
+
},
|
|
705
|
+
"gridPos": {
|
|
706
|
+
"h": 4,
|
|
707
|
+
"w": 4,
|
|
708
|
+
"x": 0,
|
|
709
|
+
"y": 1
|
|
710
|
+
},
|
|
711
|
+
"id": 2,
|
|
712
|
+
"options": {
|
|
713
|
+
"colorMode": "background",
|
|
714
|
+
"graphMode": "none",
|
|
715
|
+
"justifyMode": "auto",
|
|
716
|
+
"orientation": "auto",
|
|
717
|
+
"reduceOptions": {
|
|
718
|
+
"calcs": [
|
|
719
|
+
"lastNotNull"
|
|
720
|
+
],
|
|
721
|
+
"fields": "",
|
|
722
|
+
"values": false
|
|
723
|
+
},
|
|
724
|
+
"textMode": "auto"
|
|
725
|
+
},
|
|
726
|
+
"pluginVersion": "10.2.3",
|
|
727
|
+
"targets": [
|
|
728
|
+
{
|
|
729
|
+
"datasource": {
|
|
730
|
+
"type": "prometheus",
|
|
731
|
+
"uid": "prometheus"
|
|
732
|
+
},
|
|
733
|
+
"expr": "max(up{job=\"brawny\"})",
|
|
734
|
+
"refId": "A"
|
|
735
|
+
}
|
|
736
|
+
],
|
|
737
|
+
"title": "Brawny Up",
|
|
738
|
+
"type": "stat"
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
"datasource": {
|
|
742
|
+
"type": "prometheus",
|
|
743
|
+
"uid": "prometheus"
|
|
744
|
+
},
|
|
745
|
+
"fieldConfig": {
|
|
746
|
+
"defaults": {
|
|
747
|
+
"color": {
|
|
748
|
+
"mode": "thresholds"
|
|
749
|
+
},
|
|
750
|
+
"mappings": [],
|
|
751
|
+
"thresholds": {
|
|
752
|
+
"mode": "absolute",
|
|
753
|
+
"steps": [
|
|
754
|
+
{
|
|
755
|
+
"color": "green",
|
|
756
|
+
"value": null
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
"color": "yellow",
|
|
760
|
+
"value": 120
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
"color": "red",
|
|
764
|
+
"value": 300
|
|
765
|
+
}
|
|
766
|
+
]
|
|
767
|
+
},
|
|
768
|
+
"unit": "s"
|
|
769
|
+
},
|
|
770
|
+
"overrides": []
|
|
771
|
+
},
|
|
772
|
+
"gridPos": {
|
|
773
|
+
"h": 4,
|
|
774
|
+
"w": 4,
|
|
775
|
+
"x": 4,
|
|
776
|
+
"y": 1
|
|
777
|
+
},
|
|
778
|
+
"id": 3,
|
|
779
|
+
"options": {
|
|
780
|
+
"colorMode": "background",
|
|
781
|
+
"graphMode": "none",
|
|
782
|
+
"justifyMode": "auto",
|
|
783
|
+
"orientation": "auto",
|
|
784
|
+
"reduceOptions": {
|
|
785
|
+
"calcs": [
|
|
786
|
+
"lastNotNull"
|
|
787
|
+
],
|
|
788
|
+
"fields": "",
|
|
789
|
+
"values": false
|
|
790
|
+
},
|
|
791
|
+
"textMode": "auto"
|
|
792
|
+
},
|
|
793
|
+
"pluginVersion": "10.2.3",
|
|
794
|
+
"targets": [
|
|
795
|
+
{
|
|
796
|
+
"datasource": {
|
|
797
|
+
"type": "prometheus",
|
|
798
|
+
"uid": "prometheus"
|
|
799
|
+
},
|
|
800
|
+
"expr": "max(brawny_oldest_pending_intent_age_seconds)",
|
|
801
|
+
"refId": "A"
|
|
802
|
+
}
|
|
803
|
+
],
|
|
804
|
+
"title": "Oldest Pending Intent",
|
|
805
|
+
"type": "stat"
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
"datasource": {
|
|
809
|
+
"type": "prometheus",
|
|
810
|
+
"uid": "prometheus"
|
|
811
|
+
},
|
|
812
|
+
"fieldConfig": {
|
|
813
|
+
"defaults": {
|
|
814
|
+
"color": {
|
|
815
|
+
"mode": "thresholds"
|
|
816
|
+
},
|
|
817
|
+
"mappings": [],
|
|
818
|
+
"thresholds": {
|
|
819
|
+
"mode": "absolute",
|
|
820
|
+
"steps": [
|
|
821
|
+
{
|
|
822
|
+
"color": "green",
|
|
823
|
+
"value": null
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
"color": "yellow",
|
|
827
|
+
"value": 10
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
"color": "red",
|
|
831
|
+
"value": 50
|
|
832
|
+
}
|
|
833
|
+
]
|
|
834
|
+
},
|
|
835
|
+
"unit": "short"
|
|
836
|
+
},
|
|
837
|
+
"overrides": []
|
|
838
|
+
},
|
|
839
|
+
"gridPos": {
|
|
840
|
+
"h": 4,
|
|
841
|
+
"w": 4,
|
|
842
|
+
"x": 8,
|
|
843
|
+
"y": 1
|
|
844
|
+
},
|
|
845
|
+
"id": 4,
|
|
846
|
+
"options": {
|
|
847
|
+
"colorMode": "background",
|
|
848
|
+
"graphMode": "none",
|
|
849
|
+
"justifyMode": "auto",
|
|
850
|
+
"orientation": "auto",
|
|
851
|
+
"reduceOptions": {
|
|
852
|
+
"calcs": [
|
|
853
|
+
"lastNotNull"
|
|
854
|
+
],
|
|
855
|
+
"fields": "",
|
|
856
|
+
"values": false
|
|
857
|
+
},
|
|
858
|
+
"textMode": "auto"
|
|
859
|
+
},
|
|
860
|
+
"pluginVersion": "10.2.3",
|
|
861
|
+
"targets": [
|
|
862
|
+
{
|
|
863
|
+
"datasource": {
|
|
864
|
+
"type": "prometheus",
|
|
865
|
+
"uid": "prometheus"
|
|
866
|
+
},
|
|
867
|
+
"expr": "sum(brawny_pending_intents) or vector(0)",
|
|
868
|
+
"refId": "A"
|
|
869
|
+
}
|
|
870
|
+
],
|
|
871
|
+
"title": "Pending Intents",
|
|
872
|
+
"type": "stat"
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
"datasource": {
|
|
876
|
+
"type": "prometheus",
|
|
877
|
+
"uid": "prometheus"
|
|
878
|
+
},
|
|
879
|
+
"fieldConfig": {
|
|
880
|
+
"defaults": {
|
|
881
|
+
"color": {
|
|
882
|
+
"mode": "thresholds"
|
|
883
|
+
},
|
|
884
|
+
"mappings": [],
|
|
885
|
+
"thresholds": {
|
|
886
|
+
"mode": "absolute",
|
|
887
|
+
"steps": [
|
|
888
|
+
{
|
|
889
|
+
"color": "green",
|
|
890
|
+
"value": null
|
|
891
|
+
}
|
|
892
|
+
]
|
|
893
|
+
},
|
|
894
|
+
"unit": "short"
|
|
895
|
+
},
|
|
896
|
+
"overrides": []
|
|
897
|
+
},
|
|
898
|
+
"gridPos": {
|
|
899
|
+
"h": 4,
|
|
900
|
+
"w": 4,
|
|
901
|
+
"x": 16,
|
|
902
|
+
"y": 1
|
|
903
|
+
},
|
|
904
|
+
"id": 6,
|
|
905
|
+
"options": {
|
|
906
|
+
"colorMode": "value",
|
|
907
|
+
"graphMode": "area",
|
|
908
|
+
"justifyMode": "auto",
|
|
909
|
+
"orientation": "auto",
|
|
910
|
+
"reduceOptions": {
|
|
911
|
+
"calcs": [
|
|
912
|
+
"lastNotNull"
|
|
913
|
+
],
|
|
914
|
+
"fields": "",
|
|
915
|
+
"values": false
|
|
916
|
+
},
|
|
917
|
+
"textMode": "auto"
|
|
918
|
+
},
|
|
919
|
+
"pluginVersion": "10.2.3",
|
|
920
|
+
"targets": [
|
|
921
|
+
{
|
|
922
|
+
"datasource": {
|
|
923
|
+
"type": "prometheus",
|
|
924
|
+
"uid": "prometheus"
|
|
925
|
+
},
|
|
926
|
+
"expr": "sum(brawny_blocks_processed_total) or vector(0)",
|
|
927
|
+
"refId": "A"
|
|
928
|
+
}
|
|
929
|
+
],
|
|
930
|
+
"title": "Blocks Processed",
|
|
931
|
+
"type": "stat"
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
"datasource": {
|
|
935
|
+
"type": "prometheus",
|
|
936
|
+
"uid": "prometheus"
|
|
937
|
+
},
|
|
938
|
+
"fieldConfig": {
|
|
939
|
+
"defaults": {
|
|
940
|
+
"color": {
|
|
941
|
+
"mode": "thresholds"
|
|
942
|
+
},
|
|
943
|
+
"mappings": [],
|
|
944
|
+
"thresholds": {
|
|
945
|
+
"mode": "absolute",
|
|
946
|
+
"steps": [
|
|
947
|
+
{
|
|
948
|
+
"color": "green",
|
|
949
|
+
"value": null
|
|
950
|
+
}
|
|
951
|
+
]
|
|
952
|
+
},
|
|
953
|
+
"unit": "short"
|
|
954
|
+
},
|
|
955
|
+
"overrides": []
|
|
956
|
+
},
|
|
957
|
+
"gridPos": {
|
|
958
|
+
"h": 4,
|
|
959
|
+
"w": 4,
|
|
960
|
+
"x": 20,
|
|
961
|
+
"y": 1
|
|
962
|
+
},
|
|
963
|
+
"id": 7,
|
|
964
|
+
"options": {
|
|
965
|
+
"colorMode": "value",
|
|
966
|
+
"graphMode": "area",
|
|
967
|
+
"justifyMode": "auto",
|
|
968
|
+
"orientation": "auto",
|
|
969
|
+
"reduceOptions": {
|
|
970
|
+
"calcs": [
|
|
971
|
+
"lastNotNull"
|
|
972
|
+
],
|
|
973
|
+
"fields": "",
|
|
974
|
+
"values": false
|
|
975
|
+
},
|
|
976
|
+
"textMode": "auto"
|
|
977
|
+
},
|
|
978
|
+
"pluginVersion": "10.2.3",
|
|
979
|
+
"targets": [
|
|
980
|
+
{
|
|
981
|
+
"datasource": {
|
|
982
|
+
"type": "prometheus",
|
|
983
|
+
"uid": "prometheus"
|
|
984
|
+
},
|
|
985
|
+
"expr": "sum(brawny_tx_confirmed_total) or vector(0)",
|
|
986
|
+
"refId": "A"
|
|
987
|
+
}
|
|
988
|
+
],
|
|
989
|
+
"title": "TX Confirmed",
|
|
990
|
+
"type": "stat"
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
"gridPos": {
|
|
994
|
+
"h": 1,
|
|
995
|
+
"w": 24,
|
|
996
|
+
"x": 0,
|
|
997
|
+
"y": 5
|
|
998
|
+
},
|
|
999
|
+
"id": 10,
|
|
1000
|
+
"title": "Activity",
|
|
1001
|
+
"type": "row"
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
"datasource": {
|
|
1005
|
+
"type": "prometheus",
|
|
1006
|
+
"uid": "prometheus"
|
|
1007
|
+
},
|
|
1008
|
+
"fieldConfig": {
|
|
1009
|
+
"defaults": {
|
|
1010
|
+
"color": {
|
|
1011
|
+
"mode": "palette-classic"
|
|
1012
|
+
},
|
|
1013
|
+
"custom": {
|
|
1014
|
+
"axisBorderShow": false,
|
|
1015
|
+
"axisCenteredZero": false,
|
|
1016
|
+
"axisColorMode": "text",
|
|
1017
|
+
"axisLabel": "",
|
|
1018
|
+
"axisPlacement": "auto",
|
|
1019
|
+
"barAlignment": 0,
|
|
1020
|
+
"drawStyle": "line",
|
|
1021
|
+
"fillOpacity": 10,
|
|
1022
|
+
"gradientMode": "none",
|
|
1023
|
+
"hideFrom": {
|
|
1024
|
+
"legend": false,
|
|
1025
|
+
"tooltip": false,
|
|
1026
|
+
"viz": false
|
|
1027
|
+
},
|
|
1028
|
+
"insertNulls": false,
|
|
1029
|
+
"lineInterpolation": "linear",
|
|
1030
|
+
"lineWidth": 1,
|
|
1031
|
+
"pointSize": 5,
|
|
1032
|
+
"scaleDistribution": {
|
|
1033
|
+
"type": "linear"
|
|
1034
|
+
},
|
|
1035
|
+
"showPoints": "never",
|
|
1036
|
+
"spanNulls": false,
|
|
1037
|
+
"stacking": {
|
|
1038
|
+
"group": "A",
|
|
1039
|
+
"mode": "none"
|
|
1040
|
+
},
|
|
1041
|
+
"thresholdsStyle": {
|
|
1042
|
+
"mode": "line"
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
1045
|
+
"mappings": [],
|
|
1046
|
+
"thresholds": {
|
|
1047
|
+
"mode": "absolute",
|
|
1048
|
+
"steps": [
|
|
1049
|
+
{
|
|
1050
|
+
"color": "green",
|
|
1051
|
+
"value": null
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
"color": "yellow",
|
|
1055
|
+
"value": 120
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
"color": "red",
|
|
1059
|
+
"value": 300
|
|
1060
|
+
}
|
|
1061
|
+
]
|
|
1062
|
+
},
|
|
1063
|
+
"unit": "s"
|
|
1064
|
+
},
|
|
1065
|
+
"overrides": []
|
|
1066
|
+
},
|
|
1067
|
+
"gridPos": {
|
|
1068
|
+
"h": 8,
|
|
1069
|
+
"w": 12,
|
|
1070
|
+
"x": 0,
|
|
1071
|
+
"y": 6
|
|
1072
|
+
},
|
|
1073
|
+
"id": 11,
|
|
1074
|
+
"options": {
|
|
1075
|
+
"legend": {
|
|
1076
|
+
"calcs": [],
|
|
1077
|
+
"displayMode": "list",
|
|
1078
|
+
"placement": "bottom",
|
|
1079
|
+
"showLegend": true
|
|
1080
|
+
},
|
|
1081
|
+
"tooltip": {
|
|
1082
|
+
"mode": "multi",
|
|
1083
|
+
"sort": "none"
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
"pluginVersion": "10.2.3",
|
|
1087
|
+
"targets": [
|
|
1088
|
+
{
|
|
1089
|
+
"datasource": {
|
|
1090
|
+
"type": "prometheus",
|
|
1091
|
+
"uid": "prometheus"
|
|
1092
|
+
},
|
|
1093
|
+
"expr": "max(brawny_block_processing_lag_seconds)",
|
|
1094
|
+
"legendFormat": "seconds behind chain head",
|
|
1095
|
+
"refId": "A"
|
|
1096
|
+
}
|
|
1097
|
+
],
|
|
1098
|
+
"title": "Block Processing Lag",
|
|
1099
|
+
"type": "timeseries"
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
"datasource": {
|
|
1103
|
+
"type": "prometheus",
|
|
1104
|
+
"uid": "prometheus"
|
|
1105
|
+
},
|
|
1106
|
+
"fieldConfig": {
|
|
1107
|
+
"defaults": {
|
|
1108
|
+
"color": {
|
|
1109
|
+
"mode": "palette-classic"
|
|
1110
|
+
},
|
|
1111
|
+
"custom": {
|
|
1112
|
+
"axisBorderShow": false,
|
|
1113
|
+
"axisCenteredZero": false,
|
|
1114
|
+
"axisColorMode": "text",
|
|
1115
|
+
"axisLabel": "",
|
|
1116
|
+
"axisPlacement": "auto",
|
|
1117
|
+
"barAlignment": 0,
|
|
1118
|
+
"drawStyle": "line",
|
|
1119
|
+
"fillOpacity": 10,
|
|
1120
|
+
"gradientMode": "none",
|
|
1121
|
+
"hideFrom": {
|
|
1122
|
+
"legend": false,
|
|
1123
|
+
"tooltip": false,
|
|
1124
|
+
"viz": false
|
|
1125
|
+
},
|
|
1126
|
+
"insertNulls": false,
|
|
1127
|
+
"lineInterpolation": "linear",
|
|
1128
|
+
"lineWidth": 1,
|
|
1129
|
+
"pointSize": 5,
|
|
1130
|
+
"scaleDistribution": {
|
|
1131
|
+
"type": "linear"
|
|
1132
|
+
},
|
|
1133
|
+
"showPoints": "never",
|
|
1134
|
+
"spanNulls": false,
|
|
1135
|
+
"stacking": {
|
|
1136
|
+
"group": "A",
|
|
1137
|
+
"mode": "none"
|
|
1138
|
+
},
|
|
1139
|
+
"thresholdsStyle": {
|
|
1140
|
+
"mode": "line"
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
"mappings": [],
|
|
1144
|
+
"thresholds": {
|
|
1145
|
+
"mode": "absolute",
|
|
1146
|
+
"steps": [
|
|
1147
|
+
{
|
|
1148
|
+
"color": "green",
|
|
1149
|
+
"value": null
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
"color": "yellow",
|
|
1153
|
+
"value": 120
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
"color": "red",
|
|
1157
|
+
"value": 300
|
|
1158
|
+
}
|
|
1159
|
+
]
|
|
1160
|
+
},
|
|
1161
|
+
"unit": "s"
|
|
1162
|
+
},
|
|
1163
|
+
"overrides": []
|
|
1164
|
+
},
|
|
1165
|
+
"gridPos": {
|
|
1166
|
+
"h": 8,
|
|
1167
|
+
"w": 12,
|
|
1168
|
+
"x": 12,
|
|
1169
|
+
"y": 6
|
|
1170
|
+
},
|
|
1171
|
+
"id": 12,
|
|
1172
|
+
"options": {
|
|
1173
|
+
"legend": {
|
|
1174
|
+
"calcs": [],
|
|
1175
|
+
"displayMode": "list",
|
|
1176
|
+
"placement": "bottom",
|
|
1177
|
+
"showLegend": true
|
|
1178
|
+
},
|
|
1179
|
+
"tooltip": {
|
|
1180
|
+
"mode": "multi",
|
|
1181
|
+
"sort": "none"
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
"pluginVersion": "10.2.3",
|
|
1185
|
+
"targets": [
|
|
1186
|
+
{
|
|
1187
|
+
"datasource": {
|
|
1188
|
+
"type": "prometheus",
|
|
1189
|
+
"uid": "prometheus"
|
|
1190
|
+
},
|
|
1191
|
+
"expr": "time() - max(brawny_last_tx_confirmed_timestamp)",
|
|
1192
|
+
"legendFormat": "seconds since last TX",
|
|
1193
|
+
"refId": "B"
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
"datasource": {
|
|
1197
|
+
"type": "prometheus",
|
|
1198
|
+
"uid": "prometheus"
|
|
1199
|
+
},
|
|
1200
|
+
"expr": "time() - max(brawny_last_intent_created_timestamp)",
|
|
1201
|
+
"legendFormat": "seconds since last intent",
|
|
1202
|
+
"refId": "C"
|
|
1203
|
+
}
|
|
1204
|
+
],
|
|
1205
|
+
"title": "Activity Staleness",
|
|
1206
|
+
"type": "timeseries"
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
"gridPos": {
|
|
1210
|
+
"h": 1,
|
|
1211
|
+
"w": 24,
|
|
1212
|
+
"x": 0,
|
|
1213
|
+
"y": 14
|
|
1214
|
+
},
|
|
1215
|
+
"id": 20,
|
|
1216
|
+
"title": "Transactions",
|
|
1217
|
+
"type": "row"
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
"datasource": {
|
|
1221
|
+
"type": "prometheus",
|
|
1222
|
+
"uid": "prometheus"
|
|
1223
|
+
},
|
|
1224
|
+
"fieldConfig": {
|
|
1225
|
+
"defaults": {
|
|
1226
|
+
"color": {
|
|
1227
|
+
"mode": "palette-classic"
|
|
1228
|
+
},
|
|
1229
|
+
"custom": {
|
|
1230
|
+
"axisBorderShow": false,
|
|
1231
|
+
"axisCenteredZero": false,
|
|
1232
|
+
"axisColorMode": "text",
|
|
1233
|
+
"axisLabel": "",
|
|
1234
|
+
"axisPlacement": "auto",
|
|
1235
|
+
"barAlignment": 0,
|
|
1236
|
+
"drawStyle": "line",
|
|
1237
|
+
"fillOpacity": 10,
|
|
1238
|
+
"gradientMode": "none",
|
|
1239
|
+
"hideFrom": {
|
|
1240
|
+
"legend": false,
|
|
1241
|
+
"tooltip": false,
|
|
1242
|
+
"viz": false
|
|
1243
|
+
},
|
|
1244
|
+
"insertNulls": false,
|
|
1245
|
+
"lineInterpolation": "linear",
|
|
1246
|
+
"lineWidth": 1,
|
|
1247
|
+
"pointSize": 5,
|
|
1248
|
+
"scaleDistribution": {
|
|
1249
|
+
"type": "linear"
|
|
1250
|
+
},
|
|
1251
|
+
"showPoints": "never",
|
|
1252
|
+
"spanNulls": false,
|
|
1253
|
+
"stacking": {
|
|
1254
|
+
"group": "A",
|
|
1255
|
+
"mode": "none"
|
|
1256
|
+
},
|
|
1257
|
+
"thresholdsStyle": {
|
|
1258
|
+
"mode": "off"
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
"mappings": [],
|
|
1262
|
+
"thresholds": {
|
|
1263
|
+
"mode": "absolute",
|
|
1264
|
+
"steps": [
|
|
1265
|
+
{
|
|
1266
|
+
"color": "green",
|
|
1267
|
+
"value": null
|
|
1268
|
+
}
|
|
1269
|
+
]
|
|
1270
|
+
},
|
|
1271
|
+
"unit": "short"
|
|
1272
|
+
},
|
|
1273
|
+
"overrides": []
|
|
1274
|
+
},
|
|
1275
|
+
"gridPos": {
|
|
1276
|
+
"h": 8,
|
|
1277
|
+
"w": 12,
|
|
1278
|
+
"x": 0,
|
|
1279
|
+
"y": 15
|
|
1280
|
+
},
|
|
1281
|
+
"id": 21,
|
|
1282
|
+
"options": {
|
|
1283
|
+
"legend": {
|
|
1284
|
+
"calcs": [],
|
|
1285
|
+
"displayMode": "list",
|
|
1286
|
+
"placement": "bottom",
|
|
1287
|
+
"showLegend": true
|
|
1288
|
+
},
|
|
1289
|
+
"tooltip": {
|
|
1290
|
+
"mode": "multi",
|
|
1291
|
+
"sort": "none"
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
"pluginVersion": "10.2.3",
|
|
1295
|
+
"targets": [
|
|
1296
|
+
{
|
|
1297
|
+
"datasource": {
|
|
1298
|
+
"type": "prometheus",
|
|
1299
|
+
"uid": "prometheus"
|
|
1300
|
+
},
|
|
1301
|
+
"expr": "sum(rate(brawny_tx_broadcast_total[5m])) * 60 or vector(0)",
|
|
1302
|
+
"legendFormat": "broadcast/min",
|
|
1303
|
+
"refId": "A"
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
"datasource": {
|
|
1307
|
+
"type": "prometheus",
|
|
1308
|
+
"uid": "prometheus"
|
|
1309
|
+
},
|
|
1310
|
+
"expr": "sum(rate(brawny_tx_confirmed_total[5m])) * 60 or vector(0)",
|
|
1311
|
+
"legendFormat": "confirmed/min",
|
|
1312
|
+
"refId": "B"
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
"datasource": {
|
|
1316
|
+
"type": "prometheus",
|
|
1317
|
+
"uid": "prometheus"
|
|
1318
|
+
},
|
|
1319
|
+
"expr": "sum(rate(brawny_tx_failed_total[5m])) * 60 or vector(0)",
|
|
1320
|
+
"legendFormat": "failed/min",
|
|
1321
|
+
"refId": "C"
|
|
1322
|
+
}
|
|
1323
|
+
],
|
|
1324
|
+
"title": "TX Throughput",
|
|
1325
|
+
"type": "timeseries"
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
"datasource": {
|
|
1329
|
+
"type": "prometheus",
|
|
1330
|
+
"uid": "prometheus"
|
|
1331
|
+
},
|
|
1332
|
+
"fieldConfig": {
|
|
1333
|
+
"defaults": {
|
|
1334
|
+
"color": {
|
|
1335
|
+
"mode": "palette-classic"
|
|
1336
|
+
},
|
|
1337
|
+
"custom": {
|
|
1338
|
+
"axisBorderShow": false,
|
|
1339
|
+
"axisCenteredZero": false,
|
|
1340
|
+
"axisColorMode": "text",
|
|
1341
|
+
"axisLabel": "",
|
|
1342
|
+
"axisPlacement": "auto",
|
|
1343
|
+
"barAlignment": 0,
|
|
1344
|
+
"drawStyle": "line",
|
|
1345
|
+
"fillOpacity": 10,
|
|
1346
|
+
"gradientMode": "none",
|
|
1347
|
+
"hideFrom": {
|
|
1348
|
+
"legend": false,
|
|
1349
|
+
"tooltip": false,
|
|
1350
|
+
"viz": false
|
|
1351
|
+
},
|
|
1352
|
+
"insertNulls": false,
|
|
1353
|
+
"lineInterpolation": "linear",
|
|
1354
|
+
"lineWidth": 1,
|
|
1355
|
+
"pointSize": 5,
|
|
1356
|
+
"scaleDistribution": {
|
|
1357
|
+
"type": "linear"
|
|
1358
|
+
},
|
|
1359
|
+
"showPoints": "never",
|
|
1360
|
+
"spanNulls": false,
|
|
1361
|
+
"stacking": {
|
|
1362
|
+
"group": "A",
|
|
1363
|
+
"mode": "none"
|
|
1364
|
+
},
|
|
1365
|
+
"thresholdsStyle": {
|
|
1366
|
+
"mode": "line"
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
"mappings": [],
|
|
1370
|
+
"thresholds": {
|
|
1371
|
+
"mode": "absolute",
|
|
1372
|
+
"steps": [
|
|
1373
|
+
{
|
|
1374
|
+
"color": "green",
|
|
1375
|
+
"value": null
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
"color": "yellow",
|
|
1379
|
+
"value": 120
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
"color": "red",
|
|
1383
|
+
"value": 300
|
|
1384
|
+
}
|
|
1385
|
+
]
|
|
1386
|
+
},
|
|
1387
|
+
"unit": "s"
|
|
1388
|
+
},
|
|
1389
|
+
"overrides": []
|
|
1390
|
+
},
|
|
1391
|
+
"gridPos": {
|
|
1392
|
+
"h": 8,
|
|
1393
|
+
"w": 12,
|
|
1394
|
+
"x": 12,
|
|
1395
|
+
"y": 15
|
|
1396
|
+
},
|
|
1397
|
+
"id": 22,
|
|
1398
|
+
"options": {
|
|
1399
|
+
"legend": {
|
|
1400
|
+
"calcs": [],
|
|
1401
|
+
"displayMode": "list",
|
|
1402
|
+
"placement": "bottom",
|
|
1403
|
+
"showLegend": true
|
|
1404
|
+
},
|
|
1405
|
+
"tooltip": {
|
|
1406
|
+
"mode": "multi",
|
|
1407
|
+
"sort": "none"
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
"pluginVersion": "10.2.3",
|
|
1411
|
+
"targets": [
|
|
1412
|
+
{
|
|
1413
|
+
"datasource": {
|
|
1414
|
+
"type": "prometheus",
|
|
1415
|
+
"uid": "prometheus"
|
|
1416
|
+
},
|
|
1417
|
+
"expr": "max(brawny_oldest_pending_intent_age_seconds)",
|
|
1418
|
+
"legendFormat": "oldest pending age",
|
|
1419
|
+
"refId": "A"
|
|
1420
|
+
}
|
|
1421
|
+
],
|
|
1422
|
+
"title": "Oldest Pending Intent Age",
|
|
1423
|
+
"type": "timeseries"
|
|
1424
|
+
}
|
|
1425
|
+
],
|
|
1426
|
+
"refresh": "10s",
|
|
1427
|
+
"schemaVersion": 39,
|
|
1428
|
+
"tags": [
|
|
1429
|
+
"brawny"
|
|
1430
|
+
],
|
|
1431
|
+
"templating": {
|
|
1432
|
+
"list": []
|
|
1433
|
+
},
|
|
1434
|
+
"time": {
|
|
1435
|
+
"from": "now-1h",
|
|
1436
|
+
"to": "now"
|
|
1437
|
+
},
|
|
1438
|
+
"timepicker": {},
|
|
1439
|
+
"timezone": "browser",
|
|
1440
|
+
"title": "Brawny Overview",
|
|
1441
|
+
"uid": "brawny-overview",
|
|
1442
|
+
"version": 1,
|
|
1443
|
+
"weekStart": ""
|
|
1444
|
+
}
|
|
1445
|
+
"""
|