From a50376fde71645779af74d728dc57a3ecd6456e1 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 17 Oct 2025 13:33:54 -0400 Subject: [PATCH 01/87] feat: add iavlx --- iavl/README.md | 147 +++++++++++++ iavl/branch_layout.go | 31 +++ iavl/branch_persisted.go | 106 ++++++++++ iavl/changeset.go | 399 +++++++++++++++++++++++++++++++++++ iavl/changeset_files.go | 177 ++++++++++++++++ iavl/changeset_info.go | 10 + iavl/changeset_writer.go | 263 +++++++++++++++++++++++ iavl/cleanup.go | 426 ++++++++++++++++++++++++++++++++++++++ iavl/commit_multi_tree.go | 127 ++++++++++++ iavl/commit_tree.go | 270 ++++++++++++++++++++++++ iavl/compactor.go | 353 +++++++++++++++++++++++++++++++ iavl/dot_graph.go | 95 +++++++++ iavl/go.mod | 48 +++++ iavl/go.sum | 202 ++++++++++++++++++ iavl/iterator.go | 139 +++++++++++++ iavl/kvlog.go | 51 +++++ iavl/kvlog_writer.go | 115 ++++++++++ iavl/leaf_layout.go | 27 +++ iavl/leaf_persisted.go | 88 ++++++++ iavl/mem_node.go | 117 +++++++++++ iavl/mmap.go | 87 ++++++++ iavl/multi_tree.go | 14 ++ iavl/node.go | 21 ++ iavl/node_hash.go | 98 +++++++++ iavl/node_id.go | 121 +++++++++++ iavl/node_pointer.go | 31 +++ iavl/node_update.go | 357 ++++++++++++++++++++++++++++++++ iavl/options.go | 95 +++++++++ iavl/reader.go | 135 ++++++++++++ iavl/tree.go | 87 ++++++++ iavl/tree_store.go | 323 +++++++++++++++++++++++++++++ iavl/tree_test.go | 402 +++++++++++++++++++++++++++++++++++ iavl/update.go | 37 ++++ iavl/verify.go | 163 +++++++++++++++ iavl/version_info.go | 27 +++ iavl/writer.go | 62 ++++++ 36 files changed, 5251 insertions(+) create mode 100644 iavl/README.md create mode 100644 iavl/branch_layout.go create mode 100644 iavl/branch_persisted.go create mode 100644 iavl/changeset.go create mode 100644 iavl/changeset_files.go create mode 100644 iavl/changeset_info.go create mode 100644 iavl/changeset_writer.go create mode 100644 iavl/cleanup.go create mode 100644 iavl/commit_multi_tree.go create mode 100644 iavl/commit_tree.go create mode 100644 iavl/compactor.go create mode 100644 iavl/dot_graph.go create mode 100644 iavl/go.mod create mode 100644 iavl/go.sum create mode 100644 iavl/iterator.go create mode 100644 iavl/kvlog.go create mode 100644 iavl/kvlog_writer.go create mode 100644 iavl/leaf_layout.go create mode 100644 iavl/leaf_persisted.go create mode 100644 iavl/mem_node.go create mode 100644 iavl/mmap.go create mode 100644 iavl/multi_tree.go create mode 100644 iavl/node.go create mode 100644 iavl/node_hash.go create mode 100644 iavl/node_id.go create mode 100644 iavl/node_pointer.go create mode 100644 iavl/node_update.go create mode 100644 iavl/options.go create mode 100644 iavl/reader.go create mode 100644 iavl/tree.go create mode 100644 iavl/tree_store.go create mode 100644 iavl/tree_test.go create mode 100644 iavl/update.go create mode 100644 iavl/verify.go create mode 100644 iavl/version_info.go create mode 100644 iavl/writer.go diff --git a/iavl/README.md b/iavl/README.md new file mode 100644 index 000000000000..5905be10f32c --- /dev/null +++ b/iavl/README.md @@ -0,0 +1,147 @@ +# iavl + +## Code Organization + +### Node Types, Memory & Disk Layouts + +Much of this code was influenced by memiavl and sometimes even copied directly from it. +The `NodeID` design is mainly from iavl/v2. +The `NodePointer` design introduces the possibility of doing node eviction similar to iavl/v2, +but with non-blocking thread safety using `atomic.Pointer` so that eviction can happen in the background without +blocking reads or writes. + +* `node.go`: the `Node` interface which all 3 node types implement (`MemNode`, `BranchPersisted`, `LeafPersisted`) +* `mem_node.go`: in-memory node structure, new nodes always use the `MemNode` type +* `node_pointer.go`: all child references are wrapped in `NodePointer` which can point to either an in-memory node or an + on-disk node, or both (if the node has been written and node evicted) +* `node_id.go`: defines `NodeID` (version + index + leaf) and `NodeRef` (either a `NodeID` or a node offset in the + changeset file) +* `branch_layout.go`: defines the on-disk layout for branch nodes +* `leaf_layout.go`: defines the on-disk layout for leaf nodes +* `branch_persisted.go`: a wrapper around `BranchLayout` which implements the `Node` interface and also tracks a store + reference +* `leaf_persisted.go`: a wrapper around `LeafLayout` which implements the `Node` interface and also tracks a store + reference + +### Tree Management & Updating + +For managing tree state, we define two core types `Tree` and `CommitTree`. +We directly read from and apply updates to `Tree`s but these updates only affect the persistent state of the tree if +they are applied and committed to a `CommitTree`. + +* `tree.go`: a `Tree` struct which implements the Cosmos SDK `KVStore` interface and implements the key methods (get, + set, + delete, commit, etc). `Tree`s can be mutated, and changes can either be committed or discarded. This is essentially an + in-memory reference to a tree at a specific version that could be used read-only or mutated ad hoc without affecting + the underlying persistent tree (say for instance in `CheckTx`). +* `commit_tree.go`: defines the `CommitTree` structure which manages the persistent tree state. Using `CommitTree` you + can + create new mutable `Tree` instance using `Branch` and decide to `Apply` its changes to the persistent tree or discard + them. Calling `Commit` flushes changes to the underlying `TreeStore` which does all of the on disk state management + and cleanup. In `CommitTree` we also have an asynchronous WAL writing process (optional) and maintain a background + eviction process. +* `update.go`: types for batching changes which can later be commited or discarded +* `node_update.go` and : the code for setting and deleting nodes and doing tree rebalancing, adapted from memiavl and + iavl/v1 +* `node_hash.go`: code for computing node hashes, adapted from memiavl and iavl/v1 +* `iterator.go`: implements the Cosmos SDK `Iterator` interface, adapted from memiavl and iavl/v1 + +### Disk State Management + +### Central Coordination + +These files are the central core of managing on-disk state across multiple changesets which may be in the process of +being written or compacted. **This is the most complex part of the codebase.** + +* `tree_store.go`: code for dispatching read operations to the correct changeset, writing commits to new changesets, + and coordinating background compaction and cleanup of old changesets +* `cleanup.go`: the actual background cleanup and compaction thread + +#### Changeset Reading, Writing and Compaction + +* `changeset_files.go`: `ChangesetFiles` represents the five files which make up a changeset: + * `kv.log`: all of the key/value pairs in the changeset, and optionally the write-ahead log for replay (this is + configurable) + * `leaves.dat`: an array of `LeafLayout` structs + * `branches.dat`: an array of `BranchLayout` structs + * `verions.dat`: an array of `VersionInfo` structs, one for each version in the changeset + * `info.dat`: a single `ChangesetInfo` struct which tracks metadata about the changeset including the range of + versions + it contains and the number of orphaned nodes +* `changeset.go`: the `Changeset` struct wraps mmap's of the five changeset files and provides + methods for reading nodes from disk and marking them as orphaned. It includes some complex code for safely disposing + of `Changeset` instances because we need to either 1) reopen the memmap to change its size, or 2) close the + `Changeset` because it has been compacted and will be deleted. This is managed using pinning, a reference count, and + atomic booleans to track eviction (the desire to dispose and delete) and disposal (the actual disposal). +* `changeset_writer.go`: code for iteratively writing changesets to disk node by node in post-order traversal order. + Node references can either be by + `NodeID` or offsets (offsets have been disabled due to some unresolved bugs) +* `compactor.go`: code for rewriting one or more changesets into a new compacted changeset, skipping + orphaned nodes and updating offsets as needed (this offset rewrite code is currently buggy and disabled) + +#### Helpers + +* `version_info.go`: defines the on-disk layout for version info records, which track the root node and other metadata + for + each version +* `changeset_info.go`: defines the on-disk layout for the changeset info record, which tracks metadata + about the entire changeset including version range and number of orphaned nodes +* `kvlog.go`: code for reading key/value pairs from the `kv.log` file +* `kvlog_writer.go`: code for writing key/value pairs to the `kv.log` file, which can be structured as a write-ahead + operation log for replay and crash recovery (reply and recovery aren't implemented yet) +* `mmap.go`: the `MmapFile` mem-map wrapper +* `writer.go`: `FileWriter` and `StructWriter` wrappers for writing raw bytes and structs to files +* `reader.go`: `StructMap` and `NodeMap` wrappers for representing memory-mapped arrays of structs and nodes + +### Multi-tree Management + +* `multi_tree.go`: wraps multiple `Tree`s into a `MultiTree` which provides a mutable way to write a tree without + committing the changes to the persistent tree immediately (can be discarded) +* `commit_multi_tree.go`: wraps multiple `CommitTree`s into a `CommitMultiTree` which provides a way to create mutable + `MultiTree`s and commit their changes to the underlying persistent trees (or discard them). This can eventually + implement `RootMultiStore` and replace the SDK's store package. `CommitMultiTree` makes the optimization of running + `Commit` in parallel across all `CommitTree`s which could improve performance. + +### Options + +Options are mantained by the `Options` struct in `options.go`. Many options have a getter which uses a default value if +the option is not set. + +The main options we're controlling now are: + +* `WriteWAL`: whether we write all updates to the kv-log as a replayable write-ahead log (WAL). If this is enabled we + will fsync the WAL either asynchronously or synchronously (based on the `WalSyncBuffer` option). Enabling WAL could + actually improve performance because we asynchronously write key/value data in advance of `CommitTree.Commit` being + called. +* `EvictDepth`: the depth of the tree beyond which we will evict nodes from memory as soon as they are on disk. This is + the main lever for controlling memory usage. Using more memory could improve performance. +* `RetainVersions`: the number of recent versions to retain when we are compacting. Eventually we also want to enable + some sort of snapshot-based compaction (retaining full trees every N versions). +* `MinCompactionSeconds`: the minimum number of seconds to wait before starting a new compaction run (note that this + currently includes the time it takes to compact). +* `CompactWAL`: whether to compact the WAL when we are compacting changesets. In the future, we can distinguish between + compacting the WAL before our first checkpoint and retaining it after the first checkpoint. +* `ChangesetMaxTarget`: the size of a changeset after which we will roll over to a new changeset for the next version. +* `CompactionMaxTarget`: the target size of a compacted changeset. When adding a new changeset into our compaction will + stay below this number, we will join multiple changesets into a single compacted changeset. +* `CompactionOrphanRatio`: the ratio of orphaned nodes in a changeset beyond which we will trigger it for early + compaction (used together with `CompactionOrphanAge`) +* `CompactionOrphanAge`: the average age of orphaned nodes in a changeset beyond which we will trigger it for early + compaction (used together with `CompactionOrphanRatio`) +* `CompactAfterVersions`: the number of versions after which we will trigger a compaction when any orphans are present, + measured in versions since the last compaction. +* `ReaderUpdateInterval`: when writing multiple versions to a changeset, the number of versions after which we will open + the changeset for reading even if it has not been completed, so that readers can access the latest versions sooner and + flush memory. Set this to a shorter interval if we want to constrain memory usage more tightly and longer if we want + to reduce the number of times memmaps are re-opened for reading. + +### Utilities + +* `dot_graph.go`: code for exporting trees to Graphviz dot graph format for visualization +* `verify.go`: code for verifying tree integrity + +### Tests + +* `tree_test.go`: the only tests we have so far. These do, however, use property-based testing so we are generating + random operation sets, applying them to both iavlx and iavl/v1 trees. At each step, we confirm that behavior is + identical, including verification of hashes and verifying that invariants are maintained. \ No newline at end of file diff --git a/iavl/branch_layout.go b/iavl/branch_layout.go new file mode 100644 index 000000000000..72d355b85ed7 --- /dev/null +++ b/iavl/branch_layout.go @@ -0,0 +1,31 @@ +package iavlx + +import ( + "fmt" + "unsafe" +) + +func init() { + if unsafe.Sizeof(BranchLayout{}) != SizeBranch { + panic(fmt.Sprintf("invalid BranchLayout size: got %d, want %d", unsafe.Sizeof(BranchLayout{}), SizeBranch)) + } +} + +const ( + SizeBranch = 72 +) + +type BranchLayout struct { + Id NodeID + Left NodeRef + Right NodeRef + KeyOffset uint32 + Height uint8 + Size uint32 // TODO 5 bytes? + OrphanVersion uint32 // TODO 5 bytes? + Hash [32]byte +} + +func (b BranchLayout) ID() NodeID { + return b.Id +} diff --git a/iavl/branch_persisted.go b/iavl/branch_persisted.go new file mode 100644 index 000000000000..3a523decb6f9 --- /dev/null +++ b/iavl/branch_persisted.go @@ -0,0 +1,106 @@ +package iavlx + +import "bytes" + +type BranchPersisted struct { + store *Changeset + selfIdx uint32 + layout BranchLayout + leftPtr, rightPtr *NodePointer +} + +func (node *BranchPersisted) ID() NodeID { + return node.layout.Id +} + +func (node *BranchPersisted) Height() uint8 { + return node.layout.Height +} + +func (node *BranchPersisted) IsLeaf() bool { + return false +} + +func (node *BranchPersisted) Size() int64 { + return int64(node.layout.Size) +} + +func (node *BranchPersisted) Version() uint32 { + return uint32(node.layout.Id.Version()) +} + +func (node *BranchPersisted) Key() ([]byte, error) { + return node.store.ReadK(node.layout.Id, node.layout.KeyOffset) +} + +func (node *BranchPersisted) Value() ([]byte, error) { + return nil, nil +} + +func (node *BranchPersisted) Left() *NodePointer { + return node.leftPtr +} + +func (node *BranchPersisted) Right() *NodePointer { + return node.rightPtr +} + +func (node *BranchPersisted) Hash() []byte { + return node.layout.Hash[:] +} + +func (node *BranchPersisted) SafeHash() []byte { + return node.layout.Hash[:] +} + +func (node *BranchPersisted) MutateBranch(version uint32) (*MemNode, error) { + key, err := node.Key() + if err != nil { + return nil, err + } + memNode := &MemNode{ + height: node.Height(), + size: node.Size(), + version: version, + key: key, + left: node.Left(), + right: node.Right(), + } + return memNode, err +} + +func (node *BranchPersisted) Get(key []byte) (value []byte, index int64, err error) { + nodeKey, err := node.Key() + if err != nil { + return nil, 0, err + } + + if bytes.Compare(key, nodeKey) < 0 { + leftNode, err := node.Left().Resolve() + if err != nil { + return nil, 0, err + } + + return leftNode.Get(key) + } + + rightNode, err := node.Right().Resolve() + if err != nil { + return nil, 0, err + } + + value, index, err = rightNode.Get(key) + if err != nil { + return nil, 0, err + } + + index += node.Size() - rightNode.Size() + return value, index, nil +} + +func (node *BranchPersisted) String() string { + //TODO implement me + panic("implement me") +} + +var _ Node = (*BranchPersisted)(nil) diff --git a/iavl/changeset.go b/iavl/changeset.go new file mode 100644 index 000000000000..3eac56a32608 --- /dev/null +++ b/iavl/changeset.go @@ -0,0 +1,399 @@ +package iavlx + +import ( + "errors" + "fmt" + "sync/atomic" + "unsafe" +) + +type Changeset struct { + files *ChangesetFiles + + treeStore *TreeStore + + info *ChangesetInfo + infoMmap *StructMmap[ChangesetInfo] + kvLog *KVLog // TODO make sure we handle compaction here too + branchesData *NodeMmap[BranchLayout] + leavesData *NodeMmap[LeafLayout] + versionsData *StructMmap[VersionInfo] + + refCount atomic.Int32 + evicted atomic.Bool + disposed atomic.Bool + dirtyBranches atomic.Bool + dirtyLeaves atomic.Bool + needsSync atomic.Bool +} + +func NewChangeset(treeStore *TreeStore) *Changeset { + return &Changeset{ + treeStore: treeStore, + } +} + +func (cr *Changeset) InitOwned(files *ChangesetFiles) error { + err := cr.InitShared(files) + if err != nil { + return err + } + cr.files = files + return nil +} + +func (cr *Changeset) InitShared(files *ChangesetFiles) error { + var err error + + cr.kvLog, err = NewKVLog(files.kvlogFile) + if err != nil { + return fmt.Errorf("failed to open KV data store: %w", err) + } + + cr.leavesData, err = NewNodeReader[LeafLayout](files.leavesFile) + if err != nil { + return fmt.Errorf("failed to open leaves data file: %w", err) + } + + cr.branchesData, err = NewNodeReader[BranchLayout](files.branchesFile) + if err != nil { + return fmt.Errorf("failed to open branches data file: %w", err) + } + + cr.versionsData, err = NewStructReader[VersionInfo](files.versionsFile) + if err != nil { + return fmt.Errorf("failed to open versions data file: %w", err) + } + + // we need a reference to the changeset info mmap to be able to flush it later when orphans are marked + cr.info = files.info + cr.infoMmap = files.infoMmap + + return nil +} + +func (cr *Changeset) getVersionInfo(version uint32) (*VersionInfo, error) { + if version < cr.info.StartVersion || version >= cr.info.StartVersion+uint32(cr.versionsData.Count()) { + return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, cr.info.StartVersion, cr.info.StartVersion+uint32(cr.versionsData.Count())-1) + } + return cr.versionsData.UnsafeItem(version - cr.info.StartVersion), nil +} + +func (cr *Changeset) ReadK(nodeId NodeID, offset uint32) (key []byte, err error) { + if cr.evicted.Load() { + return cr.treeStore.ReadK(nodeId, offset) + } + cr.Pin() + defer cr.Unpin() + + k, err := cr.kvLog.UnsafeReadK(offset) + if err != nil { + return nil, err + } + copyKey := make([]byte, len(k)) + copy(copyKey, k) + return copyKey, nil +} + +func (cr *Changeset) ReadKV(nodeId NodeID, offset uint32) (key, value []byte, err error) { + if cr.evicted.Load() { + return cr.treeStore.ReadKV(nodeId, offset) + } + cr.Pin() + defer cr.Unpin() + + // TODO add an optimization when we only want to read and copy value + k, v, err := cr.kvLog.ReadKV(offset) + if err != nil { + return nil, nil, err + } + copyKey := make([]byte, len(k)) + copy(copyKey, k) + copyValue := make([]byte, len(v)) + copy(copyValue, v) + return copyKey, copyValue, nil +} + +func (cr *Changeset) ResolveLeaf(nodeId NodeID, fileIdx uint32) (LeafLayout, error) { + if cr.evicted.Load() { + return cr.treeStore.ResolveLeaf(nodeId) + } + cr.Pin() + defer cr.Unpin() + + if fileIdx == 0 { + version := uint32(nodeId.Version()) + vi, err := cr.getVersionInfo(version) + if err != nil { + return LeafLayout{}, err + } + leaf, err := cr.leavesData.FindByID(nodeId, &vi.Leaves) + if err != nil { + return LeafLayout{}, err + } + return *leaf, nil + } else { + fileIdx-- // convert to 0-based index + return *cr.leavesData.UnsafeItem(fileIdx), nil + } +} + +func (cr *Changeset) ResolveBranch(nodeId NodeID, fileIdx uint32) (BranchLayout, error) { + if cr.evicted.Load() { + return cr.treeStore.ResolveBranch(nodeId) + } + + layout, _, err := cr.resolveBranchWithIdx(nodeId, fileIdx) + return layout, err +} + +func (cr *Changeset) resolveBranchWithIdx(nodeId NodeID, fileIdx uint32) (BranchLayout, uint32, error) { + cr.Pin() + defer cr.Unpin() + + if fileIdx == 0 { + version := uint32(nodeId.Version()) + vi, err := cr.getVersionInfo(version) + if err != nil { + return BranchLayout{}, 0, err + } + branch, err := cr.branchesData.FindByID(nodeId, &vi.Branches) + if err != nil { + return BranchLayout{}, 0, err + } + // Compute the actual file index from the pointer + itemIdx := uint32((uintptr(unsafe.Pointer(branch)) - uintptr(unsafe.Pointer(&cr.branchesData.items[0]))) / uintptr(cr.branchesData.size)) + return *branch, itemIdx + 1, nil // +1 to convert back to 1-based + } else { + itemIdx := fileIdx - 1 // convert to 0-based index + return *cr.branchesData.UnsafeItem(itemIdx), fileIdx, nil // return original fileIdx + } +} + +func (cr *Changeset) resolveNodeRef(nodeRef NodeRef, selfIdx uint32) *NodePointer { + if nodeRef.IsNodeID() { + id := nodeRef.AsNodeID() + return &NodePointer{ + id: id, + store: cr.treeStore.getChangesetForVersion(uint32(id.Version())), + } + } + relPtr := nodeRef.AsRelativePointer() + offset := relPtr.Offset() + if nodeRef.IsLeaf() { + if offset < 1 { + panic(fmt.Sprintf("invalid leaf offset: %d", offset)) + } + itemIdx := uint32(offset - 1) + if itemIdx >= uint32(cr.leavesData.Count()) { + panic(fmt.Sprintf("leaf offset %d out of bounds (have %d leaves)", offset, cr.leavesData.Count())) + } + layout := cr.leavesData.UnsafeItem(itemIdx) + return &NodePointer{ + id: layout.Id, + store: cr, + fileIdx: uint32(offset), + } + } else { + idx := int64(selfIdx) + offset + if idx < 1 { + panic(fmt.Sprintf("invalid branch index: %d (selfIdx=%d, offset=%d)", idx, selfIdx, offset)) + } + itemIdx := uint32(idx - 1) + if itemIdx >= uint32(cr.branchesData.Count()) { + panic(fmt.Sprintf("branch index %d out of bounds (have %d branches)", idx, cr.branchesData.Count())) + } + layout := cr.branchesData.UnsafeItem(itemIdx) + return &NodePointer{ + id: layout.Id, + store: cr, + fileIdx: uint32(idx), + } + } +} + +func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { + if cr.evicted.Load() { + return cr.treeStore.Resolve(nodeId, fileIdx) + } + cr.Pin() + defer cr.Unpin() + + if nodeId.IsLeaf() { + layout, err := cr.ResolveLeaf(nodeId, fileIdx) + if err != nil { + return nil, err + } + return &LeafPersisted{layout: layout, store: cr}, nil + } else { + layout, actualIdx, err := cr.resolveBranchWithIdx(nodeId, fileIdx) + if err != nil { + return nil, err + } + + leftPtr := cr.resolveNodeRef(layout.Left, actualIdx) + rightPtr := cr.resolveNodeRef(layout.Right, actualIdx) + + return &BranchPersisted{ + layout: layout, + store: cr, + selfIdx: actualIdx, + leftPtr: leftPtr, + rightPtr: rightPtr, + }, nil + } +} + +var ErrDisposed = errors.New("changeset disposed") + +func (cr *Changeset) MarkOrphan(version uint32, nodeId NodeID) error { + if cr.evicted.Load() { + return ErrDisposed + } + cr.Pin() + defer cr.Unpin() + + nodeVersion := uint32(nodeId.Version()) + vi, err := cr.getVersionInfo(nodeVersion) + if err != nil { + return err + } + + if nodeId.IsLeaf() { + leaf, err := cr.leavesData.FindByID(nodeId, &vi.Leaves) + if err != nil { + return err + } + + if leaf.OrphanVersion == 0 { + leaf.OrphanVersion = version + cr.info.LeafOrphans++ + cr.info.LeafOrphanVersionTotal += uint64(version) + cr.dirtyLeaves.Store(true) + } + } else { + branch, err := cr.branchesData.FindByID(nodeId, &vi.Branches) + if err != nil { + return err + } + + if branch.OrphanVersion == 0 { + branch.OrphanVersion = version + cr.info.BranchOrphans++ + cr.info.BranchOrphanVersionTotal += uint64(version) + cr.dirtyBranches.Store(true) + } + } + + return nil +} + +func (cr *Changeset) ReadyToCompact(orphanPercentTarget float64, orphanAgeTarget uint32) bool { + leafOrphanCount := cr.info.LeafOrphans + if leafOrphanCount > 0 { + leafOrphanPercent := float64(leafOrphanCount) / float64(cr.leavesData.Count()) + leafOrphanAge := uint32(cr.info.LeafOrphanVersionTotal / uint64(cr.info.LeafOrphans)) + + if leafOrphanPercent >= orphanPercentTarget && leafOrphanAge <= orphanAgeTarget { + return true + } + } + + branchOrphanCount := cr.info.BranchOrphans + if branchOrphanCount > 0 { + branchOrphanPercent := float64(branchOrphanCount) / float64(cr.branchesData.Count()) + branchOrphanAge := uint32(cr.info.BranchOrphanVersionTotal / uint64(cr.info.BranchOrphans)) + if branchOrphanPercent >= orphanPercentTarget && branchOrphanAge <= orphanAgeTarget { + return true + } + } + + return false +} + +func (cr *Changeset) FlushOrphans() error { + cr.Pin() + defer cr.Unpin() + + wasDirty := false + if cr.dirtyLeaves.Load() { + wasDirty = true + err := cr.leavesData.Flush() + if err != nil { + return fmt.Errorf("failed to flush leaf data: %w", err) + } + cr.dirtyLeaves.Store(false) + } + if cr.dirtyBranches.Load() { + wasDirty = true + err := cr.branchesData.Flush() + if err != nil { + return fmt.Errorf("failed to flush branch data: %w", err) + } + cr.dirtyBranches.Store(false) + } + if wasDirty { + err := cr.infoMmap.Flush() + if err != nil { + return fmt.Errorf("failed to flush changeset info: %w", err) + } + } + return nil +} + +func (cr *Changeset) Close() error { + errs := []error{ + cr.kvLog.Close(), + cr.leavesData.Close(), + cr.branchesData.Close(), + cr.versionsData.Close(), + } + if cr.files != nil { + errs = append(errs, cr.files.Close()) + } + return errors.Join(errs...) +} + +func (cr *Changeset) Pin() { + cr.refCount.Add(1) +} + +func (cr *Changeset) Unpin() { + cr.refCount.Add(-1) +} + +func (cr *Changeset) Evict() { + cr.evicted.Store(true) +} + +func (cr *Changeset) TryDispose() bool { + if cr.disposed.Load() { + return true + } + if cr.refCount.Load() <= 0 { + if cr.disposed.CompareAndSwap(false, true) { + _ = cr.Close() + cr.versionsData = nil + cr.branchesData = nil + cr.leavesData = nil + cr.kvLog = nil + cr.info = nil + cr.infoMmap = nil + // DO NOT set treeStore to nil, as deposed changesets should still forward calls to the main tree store + // DO NOT set files to nil, as we might need to delete them later + return true + } + } + return false +} + +func (cr *Changeset) TotalBytes() int { + return cr.leavesData.TotalBytes() + + cr.branchesData.TotalBytes() + + cr.kvLog.TotalBytes() + + cr.versionsData.TotalBytes() +} + +func (cr *Changeset) HasOrphans() bool { + return cr.info.LeafOrphans > 0 || cr.info.BranchOrphans > 0 +} diff --git a/iavl/changeset_files.go b/iavl/changeset_files.go new file mode 100644 index 000000000000..79f8e66aae96 --- /dev/null +++ b/iavl/changeset_files.go @@ -0,0 +1,177 @@ +package iavlx + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sync/atomic" +) + +type ChangesetFiles struct { + dir string + treeDir string + startVersion uint32 + compactedAt uint32 + + kvlogFile *os.File + branchesFile *os.File + leavesFile *os.File + versionsFile *os.File + infoFile *os.File + + info *ChangesetInfo + infoMmap *StructMmap[ChangesetInfo] + + closed bool + needsSync atomic.Bool +} + +func OpenChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogPath string) (*ChangesetFiles, error) { + // ensure absolute path + var err error + treeDir, err = filepath.Abs(treeDir) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path for %s: %w", treeDir, err) + } + + dirName := fmt.Sprintf("%d", startVersion) + if compactedAt > 0 { + dirName = fmt.Sprintf("%d.%d", startVersion, compactedAt) + } + dir := filepath.Join(treeDir, dirName) + + err = os.MkdirAll(dir, 0o755) + if err != nil { + return nil, fmt.Errorf("failed to create changeset dir: %w", err) + } + + if kvlogPath == "" { + kvlogPath = filepath.Join(dir, "kv.log") + } + kvlogFile, err := os.OpenFile(kvlogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to create KV log file: %w", err) + } + + leavesPath := filepath.Join(dir, "leaves.dat") + leavesFile, err := os.OpenFile(leavesPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to create leaves data file: %w", err) + } + + branchesPath := filepath.Join(dir, "branches.dat") + branchesFile, err := os.OpenFile(branchesPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to create branches data file: %w", err) + } + + versionsPath := filepath.Join(dir, "versions.dat") + versionsFile, err := os.OpenFile(versionsPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to create versions data file: %w", err) + } + + infoPath := filepath.Join(dir, "info.dat") + infoFile, err := os.OpenFile(infoPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to create changeset info file: %w", err) + } + + // check file size to see if we need to initialize + stat, err := infoFile.Stat() + if err != nil { + return nil, fmt.Errorf("failed to stat info file: %w", err) + } + + if stat.Size() == 0 { + // file is empty, initialize it + infoWriter := NewStructWriter[ChangesetInfo](infoFile) + if err := infoWriter.Append(&ChangesetInfo{}); err != nil { + return nil, fmt.Errorf("failed to write initial changeset info: %w", err) + } + if err := infoWriter.Flush(); err != nil { + return nil, fmt.Errorf("failed to flush initial changeset info: %w", err) + } + } + + // now create the mmap reader + infoMmap, err := NewStructReader[ChangesetInfo](infoFile) + if err != nil { + return nil, fmt.Errorf("failed to open changeset info: %w", err) + } + + if infoMmap.Count() != 1 { + return nil, fmt.Errorf("changeset info file has unexpected item count: %d", infoMmap.Count()) + } + + return &ChangesetFiles{ + dir: dir, + treeDir: treeDir, + startVersion: startVersion, + compactedAt: compactedAt, + kvlogFile: kvlogFile, + branchesFile: branchesFile, + leavesFile: leavesFile, + versionsFile: versionsFile, + infoFile: infoFile, + info: infoMmap.UnsafeItem(0), + infoMmap: infoMmap, + }, nil +} + +func (cr *ChangesetFiles) TreeDir() string { + return cr.treeDir +} + +func (cr *ChangesetFiles) KVLogPath() string { + return cr.kvlogFile.Name() +} + +func (cr *ChangesetFiles) StartVersion() uint32 { + return cr.startVersion +} + +func (cr *ChangesetFiles) CompactedAtVersion() uint32 { + return cr.compactedAt +} + +type ChangesetDeleteArgs struct { + SaveKVLogPath string +} + +func (cr *ChangesetFiles) Close() error { + if cr.closed { + return nil + } + + cr.closed = true + cr.info = nil + return errors.Join( + cr.kvlogFile.Close(), + cr.branchesFile.Close(), + cr.leavesFile.Close(), + cr.versionsFile.Close(), + cr.infoFile.Close(), + cr.infoMmap.Close(), + ) +} + +func (cr *ChangesetFiles) DeleteFiles(args ChangesetDeleteArgs) error { + errs := []error{ + os.Remove(cr.infoFile.Name()), + os.Remove(cr.leavesFile.Name()), + os.Remove(cr.branchesFile.Name()), + os.Remove(cr.versionsFile.Name()), + } + if cr.kvlogFile.Name() != args.SaveKVLogPath { + errs = append(errs, os.Remove(cr.kvlogFile.Name())) + } + err := errors.Join(errs...) + if err != nil { + return fmt.Errorf("failed to delete changeset files: %w", err) + } + // delete dir if empty + _ = os.Remove(cr.dir) + return nil +} diff --git a/iavl/changeset_info.go b/iavl/changeset_info.go new file mode 100644 index 000000000000..45bec4e08dd7 --- /dev/null +++ b/iavl/changeset_info.go @@ -0,0 +1,10 @@ +package iavlx + +type ChangesetInfo struct { + StartVersion uint32 + EndVersion uint32 + LeafOrphans uint32 + BranchOrphans uint32 + LeafOrphanVersionTotal uint64 + BranchOrphanVersionTotal uint64 +} diff --git a/iavl/changeset_writer.go b/iavl/changeset_writer.go new file mode 100644 index 000000000000..689d258a7061 --- /dev/null +++ b/iavl/changeset_writer.go @@ -0,0 +1,263 @@ +package iavlx + +import ( + "errors" + "fmt" +) + +type ChangesetWriter struct { + stagedVersion uint32 + + files *ChangesetFiles + + kvlog *KVLogWriter + branchesData *StructWriter[BranchLayout] + leavesData *StructWriter[LeafLayout] + versionsData *StructWriter[VersionInfo] + + reader *Changeset + + keyCache map[string]uint32 +} + +func NewChangesetWriter(treeDir string, startVersion uint32, treeStore *TreeStore) (*ChangesetWriter, error) { + files, err := OpenChangesetFiles(treeDir, startVersion, 0, "") + if err != nil { + return nil, fmt.Errorf("failed to open changeset files: %w", err) + } + + cs := &ChangesetWriter{ + stagedVersion: startVersion, + files: files, + kvlog: NewKVDataWriter(files.kvlogFile), + branchesData: NewStructWriter[BranchLayout](files.branchesFile), + leavesData: NewStructWriter[LeafLayout](files.leavesFile), + versionsData: NewStructWriter[VersionInfo](files.versionsFile), + reader: NewChangeset(treeStore), + keyCache: make(map[string]uint32), + } + return cs, nil +} + +func (cs *ChangesetWriter) WriteWALUpdates(updates []KVUpdate) error { + return cs.kvlog.WriteUpdates(updates) +} + +func (cs *ChangesetWriter) WriteWALCommit(version uint32) error { + return cs.kvlog.WriteCommit(version) +} + +func (cs *ChangesetWriter) SaveRoot(root *NodePointer, version uint32, totalLeaves, totalBranches uint32) error { + if version != cs.stagedVersion { + return fmt.Errorf("version mismatch: expected %d, got %d", cs.stagedVersion, version) + } + + var versionInfo VersionInfo + versionInfo.Branches.StartOffset = uint32(cs.branchesData.Count()) + versionInfo.Leaves.StartOffset = uint32(cs.leavesData.Count()) + if totalBranches > 0 { + versionInfo.Branches.StartIndex = 1 + versionInfo.Branches.Count = totalBranches + versionInfo.Branches.EndIndex = totalBranches + } + if totalLeaves > 0 { + versionInfo.Leaves.StartIndex = 1 + versionInfo.Leaves.Count = totalLeaves + versionInfo.Leaves.EndIndex = totalLeaves + } + + if root != nil { + err := cs.writeNode(root) + if err != nil { + return err + } + + versionInfo.RootID = root.id + } + + // commit version info + err := cs.versionsData.Append(&versionInfo) + if err != nil { + return fmt.Errorf("failed to write version info: %w", err) + } + + // Set start version on first successful save + info := cs.files.info + if info.StartVersion == 0 { + info.StartVersion = version + } + + // Always update end version + info.EndVersion = version + + cs.stagedVersion++ + + return nil +} + +func (cs *ChangesetWriter) CreatedSharedReader() (*Changeset, error) { + err := cs.Flush() + if err != nil { + return nil, fmt.Errorf("failed to flush data before creating shared reader: %w", err) + } + + err = cs.reader.InitShared(cs.files) + if err != nil { + return nil, fmt.Errorf("failed to initialize shared changeset reader: %w", err) + } + + reader := cs.reader + cs.reader = NewChangeset(reader.treeStore) + return reader, nil +} + +func (cs *ChangesetWriter) Flush() error { + return errors.Join( + cs.files.infoMmap.Flush(), + cs.leavesData.Flush(), + cs.branchesData.Flush(), + cs.kvlog.Flush(), + cs.versionsData.Flush(), + ) +} + +func (cs *ChangesetWriter) writeNode(np *NodePointer) error { + memNode := np.mem.Load() + if memNode == nil { + return nil // already persisted + } + if memNode.version != cs.stagedVersion { + return nil // not part of this version + } + if memNode.IsLeaf() { + return cs.writeLeaf(np, memNode) + } else { + return cs.writeBranch(np, memNode) + } +} + +func (cs *ChangesetWriter) writeBranch(np *NodePointer, node *MemNode) error { + // recursively write children in post-order traversal + err := cs.writeNode(node.left) + if err != nil { + return err + } + err = cs.writeNode(node.right) + if err != nil { + return err + } + + // TODO cache key offset in memory to avoid duplicate writes + keyOffset, ok := cs.keyCache[string(node.key)] + if !ok { + var err error + keyOffset, err = cs.kvlog.WriteK(node.key) + if err != nil { + return fmt.Errorf("failed to write key data: %w", err) + } + } + + // now write parent + parentIdx := int64(cs.branchesData.Count() + 1) // +1 to account for the node being written + leftRef := cs.createNodeRef(parentIdx, node.left) + rightRef := cs.createNodeRef(parentIdx, node.right) + + layout := BranchLayout{ + Id: np.id, + Left: leftRef, + Right: rightRef, + KeyOffset: keyOffset, + Height: node.height, + Size: uint32(node.size), // TODO check overflow + OrphanVersion: 0, + } + copy(layout.Hash[:], node.hash) // TODO check length + + err = cs.branchesData.Append(&layout) // TODO check error + if err != nil { + return fmt.Errorf("failed to write branch node: %w", err) + } + + np.fileIdx = uint32(cs.branchesData.Count()) + np.store = cs.reader + + return nil +} + +func (cs *ChangesetWriter) writeLeaf(np *NodePointer, node *MemNode) error { + keyOffset := node.keyOffset + if keyOffset == 0 { + var err error + keyOffset, err = cs.kvlog.WriteKV(node.key, node.value) + if err != nil { + return fmt.Errorf("failed to write key-value data: %w", err) + } + } + + layout := LeafLayout{ + Id: np.id, + KeyOffset: keyOffset, + OrphanVersion: 0, + } + copy(layout.Hash[:], node.hash) // TODO check length + + err := cs.leavesData.Append(&layout) + if err != nil { + return fmt.Errorf("failed to write leaf node: %w", err) + } + + np.fileIdx = uint32(cs.leavesData.Count()) + np.store = cs.reader + + cs.keyCache[string(node.key)] = keyOffset + + return nil +} + +func (cs *ChangesetWriter) createNodeRef(parentIdx int64, np *NodePointer) NodeRef { + if np.store == cs.reader { + if np.id.IsLeaf() { + //return NodeRef(np.id) + return NodeRef(NewNodeRelativePointer(true, int64(np.fileIdx))) + } else { + // for branch nodes the relative offset is the difference between the parent ID index and the branch ID index + relOffset := int64(np.fileIdx) - parentIdx + return NodeRef(NewNodeRelativePointer(false, relOffset)) + } + } else { + return NodeRef(np.id) + } +} + +func (cs *ChangesetWriter) TotalBytes() int { + return cs.leavesData.Size() + + cs.branchesData.Size() + + cs.versionsData.Size() + + cs.kvlog.Size() +} + +func (cs *ChangesetWriter) Seal() (*Changeset, error) { + err := cs.Flush() + if err != nil { + return nil, fmt.Errorf("failed to flush changeset data: %w", err) + } + + err = cs.reader.InitOwned(cs.files) + if err != nil { + return nil, fmt.Errorf("failed to initialize owned changeset reader: %w", err) + } + cs.files = nil + cs.leavesData = nil + cs.branchesData = nil + cs.versionsData = nil + cs.kvlog = nil + cs.keyCache = nil + reader := cs.reader + cs.reader = nil + + return reader, nil +} + +func (cs *ChangesetWriter) StartVersion() uint32 { + return cs.files.StartVersion() +} diff --git a/iavl/cleanup.go b/iavl/cleanup.go new file mode 100644 index 000000000000..12083a31eb5d --- /dev/null +++ b/iavl/cleanup.go @@ -0,0 +1,426 @@ +package iavlx + +import ( + "errors" + "fmt" + "sync" + "time" +) + +type cleanupProc struct { + *TreeStore + closeCleanupProc chan struct{} + cleanupProcDone chan struct{} + + // Split orphan queues based on whether versions are readable + orphanWriteQueue []markOrphansReq // For versions <= savedVersion (can process immediately) + stagedOrphanQueue []markOrphansReq // For versions > savedVersion (need to wait) + orphanQueueLock sync.Mutex + + toDelete map[*Changeset]ChangesetDeleteArgs + activeCompactor *Compactor + beingCompacted []compactionEntry + + // Disposal queue for evicted changesets awaiting refcount=0 + disposalQueue sync.Map // *Changeset -> struct{} +} + +type compactionEntry struct { + entry *changesetEntry + cs *Changeset +} + +func newCleanupProc(treeStore *TreeStore) *cleanupProc { + cp := &cleanupProc{ + TreeStore: treeStore, + closeCleanupProc: make(chan struct{}), + cleanupProcDone: make(chan struct{}), + toDelete: make(map[*Changeset]ChangesetDeleteArgs), + } + go cp.run() + return cp +} + +func (cp *cleanupProc) run() { + defer close(cp.cleanupProcDone) + minCompactorInterval := time.Second * time.Duration(cp.opts.MinCompactionSeconds) + var lastCompactorStart time.Time + + for { + sleepTime := time.Duration(0) + if time.Since(lastCompactorStart) < minCompactorInterval { + sleepTime = minCompactorInterval - time.Since(lastCompactorStart) + } + select { + case <-cp.closeCleanupProc: + return + case <-time.After(sleepTime): + } + + lastCompactorStart = time.Now() + + // process any pending orphans at the start of each cycle + err := cp.doMarkOrphans() + if err != nil { + cp.logger.Error("failed to mark orphans at start of cycle", "error", err) + } + + // collect current entries + cp.changesetsMapLock.RLock() + var entries []*changesetEntry + cp.changesets.Scan(func(version uint32, entry *changesetEntry) bool { + entries = append(entries, entry) + return true + }) + cp.changesetsMapLock.RUnlock() + + for i := 0; i < len(entries); i++ { + entry := entries[i] + var nextEntry *changesetEntry + if i+1 < len(entries) { + nextEntry = entries[i+1] + } + err := cp.processEntry(entry, nextEntry) + if err != nil { + cp.logger.Error("failed to process changeset entry", "error", err) + // on error, clean up any failed compaction and stop processing further entries this round + cp.cleanupFailedCompaction() + break + } + } + if cp.activeCompactor != nil { + err := cp.sealActiveCompactor() + if err != nil { + cp.logger.Error("failed to seal active compactor", "error", err) + } + } + + cp.processToDelete() + cp.processDisposalQueue() + } +} + +func (cp *cleanupProc) markOrphans(version uint32, nodeIds [][]NodeID) { + req := markOrphansReq{ + version: version, + orphans: nodeIds, + } + + cp.orphanQueueLock.Lock() + defer cp.orphanQueueLock.Unlock() + + cp.orphanWriteQueue = append(cp.orphanWriteQueue, req) +} + +// doMarkOrphans must only be called from the cleanupProc +func (cp *cleanupProc) doMarkOrphans() error { + var orphanQueue []markOrphansReq + cp.orphanQueueLock.Lock() + orphanQueue, cp.orphanWriteQueue = cp.orphanWriteQueue, nil + cp.orphanQueueLock.Unlock() + + orphanQueue = append(orphanQueue, cp.stagedOrphanQueue...) + + savedVersion := cp.savedVersion.Load() + var newStagedOrphans []markOrphansReq + + for _, req := range orphanQueue { + for _, nodeSet := range req.orphans { + var stagedNodes []NodeID + for _, nodeId := range nodeSet { + nodeVersion := uint32(nodeId.Version()) + + // Route to staged queue if version not yet readable + if nodeVersion > savedVersion { + stagedNodes = append(stagedNodes, nodeId) + continue + } + + ce := cp.getChangesetEntryForVersion(nodeVersion) + if ce == nil { + return fmt.Errorf("no changeset found for version %d", nodeVersion) + } + // this somewhat awkward retry loop is needed to handle a race condition where + // we have disposed of a changeset between getting the entry and marking the orphan + retries := 0 + for { + err := ce.changeset.Load().MarkOrphan(req.version, nodeId) + if errors.Is(err, ErrDisposed) { + if retries > 3 { + return fmt.Errorf("changeset for version %d disposed while marking orphan %s", nodeVersion, nodeId.String()) + } + retries++ + continue + } else if err != nil { + return err + } + break + } + } + // Add any staged nodes back to the staged queue + if len(stagedNodes) > 0 { + newStagedOrphans = append(newStagedOrphans, markOrphansReq{ + version: req.version, + orphans: [][]NodeID{stagedNodes}, + }) + } + } + } + + cp.stagedOrphanQueue = newStagedOrphans + + return nil +} + +func (cp *cleanupProc) processEntry(entry, nextEntry *changesetEntry) error { + cs := entry.changeset.Load() + + if cs.files == nil { + // skipping incomplete changeset which is still open for writing + return nil + } + + // safety check - skip if evicted or disposed + if cs.evicted.Load() || cs.disposed.Load() { + return fmt.Errorf("evicted/disposed changeset: %s found in queue", cs.files.dir) + } + + // safety check - ensure info is valid + if cs.info == nil { + return fmt.Errorf("changeset has nil info: %s found in queue", cs.files.dir) + } + + err := cs.FlushOrphans() + if err != nil { + return fmt.Errorf("failed to flush orphans for changeset %s: %w", cs.files.dir, err) + } + + if cp.opts.DisableCompaction { + return nil + } + + // skip if still pending sync + if cs.needsSync.Load() { + return nil + } + + if cp.activeCompactor != nil { + if cp.opts.CompactWAL && + cs.TotalBytes()+cp.activeCompactor.TotalBytes() <= int(cp.opts.GetCompactionMaxTarget()) { + // add to active compactor + cp.logger.Debug("joining changeset to active compactor", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir, + "newDir", cp.activeCompactor.files.dir) + err = cp.activeCompactor.AddChangeset(cs) + if err != nil { + return fmt.Errorf("failed to add changeset to active compactor: %w", err) + } + cp.beingCompacted = append(cp.beingCompacted, compactionEntry{entry: entry, cs: cs}) + return nil + } else { + err = cp.sealActiveCompactor() + if err != nil { + cp.cleanupFailedCompaction() + return fmt.Errorf("failed to seal active compactor: %w", err) + } + } + } + + // mark any pending orphans here when we don't have an active compactor + err = cp.doMarkOrphans() + if err != nil { + cp.logger.Error("failed to mark orphans", "error", err) + } + + // check if other triggers apply for a new compaction + savedVersion := cp.savedVersion.Load() + retainVersions := cp.opts.RetainVersions + retentionWindowBottom := savedVersion - retainVersions + + // Skip changesets within retention window + if cs.info.EndVersion >= retentionWindowBottom { + return nil + } + + compactOrphanAge := cp.opts.GetCompactionOrphanAge() + compactOrphanThreshold := cp.opts.GetCompactionOrphanRatio() + + // Age target relative to bottom of retention window + ageTarget := retentionWindowBottom - compactOrphanAge + + // Check orphan-based trigger + shouldCompact := cs.ReadyToCompact(compactOrphanThreshold, ageTarget) + if !shouldCompact { + lastCompactedAt := cs.files.CompactedAtVersion() + if savedVersion-lastCompactedAt >= cp.opts.GetCompactAfterVersions() { + shouldCompact = cs.HasOrphans() + } + } + + // Check size-based joining trigger + maxSize := cp.opts.GetCompactionMaxTarget() + + canJoin := false + if !shouldCompact && cp.opts.CompactWAL && nextEntry != nil { + nextCs := nextEntry.changeset.Load() + if nextCs.files != nil && // we can't compact a changeset that's still being written + nextCs.info.StartVersion == cs.info.EndVersion+1 { + if uint64(cs.TotalBytes())+uint64(nextCs.TotalBytes()) <= maxSize { + canJoin = true + } + } + } + + if !shouldCompact && !canJoin { + return nil + } + + retainVersion := retentionWindowBottom + retainCriteria := func(createVersion, orphanVersion uint32) bool { + // orphanVersion should be non-zero + if orphanVersion >= retainVersion { + // keep the orphan if it's in the retain window + return true + } else { + // otherwise, we can remove it + return false + } + } + + cp.logger.Info("compacting changeset", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir) + + cp.activeCompactor, err = NewCompacter(cp.logger, cs, CompactOptions{ + RetainCriteria: retainCriteria, + CompactWAL: cp.opts.CompactWAL, + CompactedAt: savedVersion, + }, cp.TreeStore) + if err != nil { + return fmt.Errorf("failed to create compactor: %w", err) + } + cp.beingCompacted = []compactionEntry{{entry: entry, cs: cs}} + return nil +} + +func (cp *cleanupProc) sealActiveCompactor() error { + // seal compactor and finish + newCs, err := cp.activeCompactor.Seal() + if err != nil { + return fmt.Errorf("failed to seal active compactor: %w", err) + } + + // update all processed entries to point to new changeset + oldSize := uint64(0) + for i, procEntry := range cp.beingCompacted { + cp.logger.Debug("updating changeset entry to compacted changeset and trying to delete", + "old_dir", procEntry.cs.files.dir, "new_dir", newCs.files.dir) + + oldCs := procEntry.cs + oldDir := oldCs.files.dir + oldSize += uint64(oldCs.TotalBytes()) + + if i == 0 { + procEntry.entry.changeset.Store(newCs) + } else { + cp.changesetsMapLock.Lock() + cp.changesets.Delete(oldCs.files.StartVersion()) + cp.changesetsMapLock.Unlock() + } + oldCs.Evict() + + // try to delete now or schedule for later + if !oldCs.TryDispose() { + cp.logger.Debug("changeset has active references, scheduling for deletion", "path", oldDir, "refcount", oldCs.refCount.Load()) + cp.toDelete[oldCs] = ChangesetDeleteArgs{newCs.files.KVLogPath()} + } else { + cp.logger.Info("changeset disposed, deleting files", "path", oldDir) + err = oldCs.files.DeleteFiles(ChangesetDeleteArgs{SaveKVLogPath: newCs.files.KVLogPath()}) + if err != nil { + cp.logger.Error("failed to delete old changeset files", "error", err, "path", oldDir) + } + } + } + + cp.logger.Info("compacted changeset", "dir", newCs.files.dir, "new_size", newCs.TotalBytes(), "old_size", oldSize, "joined", len(cp.beingCompacted)) + + // Clear compactor state after successful seal + cp.activeCompactor = nil + cp.beingCompacted = nil + return nil +} + +func (cp *cleanupProc) cleanupFailedCompaction() { + // clean up any partial compactor state and remove temporary files + if cp.activeCompactor != nil && cp.activeCompactor.files != nil { + cp.logger.Warn("cleaning up failed compaction", "dir", cp.activeCompactor.files.dir, "changesets_attempted", len(cp.beingCompacted)) + err := cp.activeCompactor.Abort() + if err != nil { + cp.logger.Error("failed to abort active compactor", "error", err) + } + } + cp.activeCompactor = nil + cp.beingCompacted = nil +} + +func (cp *cleanupProc) processToDelete() { + if len(cp.toDelete) > 0 { + cp.logger.Debug("processing delete queue", "size", len(cp.toDelete)) + } + + for oldCs, args := range cp.toDelete { + select { + case <-cp.closeCleanupProc: + return + default: + } + + if !oldCs.TryDispose() { + cp.logger.Warn("old changeset not disposed, skipping delete", "path", oldCs.files.dir, "refcount", oldCs.refCount.Load()) + continue + } + + cp.logger.Info("deleting old changeset files", "path", oldCs.files.dir) + err := oldCs.files.DeleteFiles(args) + if err != nil { + cp.logger.Error("failed to delete old changeset files", "error", err) + } + delete(cp.toDelete, oldCs) + } +} + +func (cp *cleanupProc) shutdown() { + close(cp.closeCleanupProc) + <-cp.cleanupProcDone +} + +// addPendingDisposal adds an evicted changeset to the disposal queue +func (cp *cleanupProc) addPendingDisposal(cs *Changeset) { + cp.disposalQueue.Store(cs, struct{}{}) +} + +// processDisposalQueue tries to dispose changesets waiting for refcount=0 +func (cp *cleanupProc) processDisposalQueue() { + disposalCount := 0 + cp.disposalQueue.Range(func(key, value interface{}) bool { + disposalCount++ + return true + }) + + if disposalCount > 0 { + cp.logger.Debug("processing disposal queue", "size", disposalCount) + } + + cp.disposalQueue.Range(func(key, value interface{}) bool { + cs := key.(*Changeset) + if cs.TryDispose() { + cp.disposalQueue.Delete(cs) + cp.logger.Debug("disposed shared changeset from queue") + } else { + cp.logger.Debug("shared changeset still has references", "refcount", cs.refCount.Load()) + } + return true + }) + + // Warn if the disposal queue is getting large + if disposalCount > 100 { + cp.logger.Warn("disposal queue is large", "size", disposalCount) + } +} diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go new file mode 100644 index 000000000000..e9f736b52cde --- /dev/null +++ b/iavl/commit_multi_tree.go @@ -0,0 +1,127 @@ +package iavlx + +import ( + "fmt" + "log/slog" + "os" + "path/filepath" + + storev1beta1 "cosmossdk.io/api/cosmos/store/v1beta1" + "github.com/alitto/pond/v2" +) + +type CommitMultiTree struct { + trees []*CommitTree + treeNames []string // always ordered by tree name + treesByName map[string]int // index of the trees by name + version uint64 + commitPool pond.ResultPool[[]byte] +} + +func LoadDB(path string, treeNames []string, opts *Options, logger *slog.Logger) (*CommitMultiTree, error) { + n := len(treeNames) + trees := make([]*CommitTree, n) + treesByName := make(map[string]int, n) + for i, name := range treeNames { + if _, exists := treesByName[name]; exists { + return nil, fmt.Errorf("duplicate tree name: %s", name) + } + treesByName[name] = i + dir := filepath.Join(path, name) + err := os.MkdirAll(dir, 0o755) + if err != nil { + return nil, fmt.Errorf("failed to create tree dir %s: %w", dir, err) + } + // Create a logger with tree name context + treeLogger := logger.With("tree", name) + trees[i], err = NewCommitTree(dir, *opts, treeLogger) + if err != nil { + return nil, fmt.Errorf("failed to load tree %s: %w", name, err) + } + } + + db := &CommitMultiTree{ + trees: trees, + treeNames: treeNames, + treesByName: treesByName, + commitPool: pond.NewResultPool[[]byte](n), + } + return db, nil +} + +func (db *CommitMultiTree) stagedVersion() uint64 { + return db.version + 1 +} + +func (db *CommitMultiTree) LatestVersion() uint64 { + return db.version +} + +func (db *CommitMultiTree) Branch() *MultiTree { + mt := &MultiTree{ + trees: make([]*Tree, len(db.trees)), + treesByName: db.treesByName, // share the map + } + for i, root := range db.trees { + mt.trees[i] = root.Branch() + } + return mt +} + +func (db *CommitMultiTree) Apply(mt *MultiTree) error { + if len(mt.trees) != len(db.trees) { + return fmt.Errorf("mismatched number of trees: %d vs %d", len(mt.trees), len(db.trees)) + } + for i, tree := range mt.trees { + err := db.trees[i].Apply(tree) + if err != nil { + return fmt.Errorf("failed to apply tree %d: %w", i, err) + } + } + return nil +} + +func (db *CommitMultiTree) Commit(logger *slog.Logger) (*storev1beta1.CommitInfo, error) { + taskGroup := db.commitPool.NewGroup() + for _, tree := range db.trees { + t := tree + taskGroup.SubmitErr(func() ([]byte, error) { + if t.root == nil { + logger.Warn("skipping hash of empty tree") + } + return t.Commit() + }) + } + hashes, err := taskGroup.Wait() + if err != nil { + return nil, fmt.Errorf("failed to commit trees: %w", err) + } + db.version++ + commitInfo := &storev1beta1.CommitInfo{ + Version: int64(db.version), + StoreInfos: make([]*storev1beta1.StoreInfo, len(db.trees)), + } + for i, treeName := range db.treeNames { + if hashes[i] == nil { + return nil, fmt.Errorf("tree %s returned nil hash", treeName) + } + commitInfo.StoreInfos[i] = &storev1beta1.StoreInfo{ + Name: treeName, + CommitId: &storev1beta1.CommitID{ + Version: int64(db.version), + Hash: hashes[i], + }, + } + } + return commitInfo, nil +} + +func (db *CommitMultiTree) Close() error { + for _, tree := range db.trees { + err := tree.Close() + if err != nil { + return err + } + } + return nil +} diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go new file mode 100644 index 000000000000..d596cb745291 --- /dev/null +++ b/iavl/commit_tree.go @@ -0,0 +1,270 @@ +package iavlx + +import ( + "fmt" + "log/slog" + "sync" + "sync/atomic" +) + +type CommitTree struct { + latest atomic.Pointer[NodePointer] + root *NodePointer + version uint32 + writeMutex sync.Mutex + store *TreeStore + zeroCopy bool + + evictionDepth uint8 + evictorRunning bool + lastEvictVersion uint32 + + writeWal bool + walChan chan<- []KVUpdate + walDone <-chan error + + pendingOrphans [][]NodeID + + logger *slog.Logger +} + +func NewCommitTree(dir string, opts Options, logger *slog.Logger) (*CommitTree, error) { + ts, err := NewTreeStore(dir, opts, logger) + if err != nil { + return nil, fmt.Errorf("failed to create tree store: %w", err) + } + + tree := &CommitTree{ + root: nil, + zeroCopy: opts.ZeroCopy, + version: 0, + logger: logger, + store: ts, + evictionDepth: opts.EvictDepth, + writeWal: opts.WriteWAL, + } + tree.reinitWalProc() + + return tree, nil +} + +func (c *CommitTree) stagedVersion() uint32 { + return c.version + 1 +} + +func (c *CommitTree) reinitWalProc() { + if !c.writeWal { + return + } + + walChan := make(chan []KVUpdate, 2048) + walDone := make(chan error, 1) + c.walChan = walChan + c.walDone = walDone + + go func() { + defer close(walDone) + for updates := range walChan { + err := c.store.WriteWALUpdates(updates) + if err != nil { + walDone <- err + return + } + } + }() +} + +func (c *CommitTree) Branch() *Tree { + return NewTree(c.root, NewKVUpdateBatch(c.stagedVersion()), c.zeroCopy) +} + +func (c *CommitTree) Apply(tree *Tree) error { + // TODO check channel errors + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if tree.updateBatch.Version != c.stagedVersion() { + return fmt.Errorf("tree version %d does not match staged version %d", tree.updateBatch.Version, c.stagedVersion()) + } + if tree.origRoot != c.root { + // TODO find a way to apply the changes incrementally when roots don't match + return fmt.Errorf("tree original root does not match current root") + } + c.root = tree.root + batch := tree.updateBatch + c.pendingOrphans = append(c.pendingOrphans, batch.Orphans...) + + if c.writeWal { + c.walChan <- batch.Updates + } + + // TODO prevent further writes to the branch tree + + return nil +} + +func (c *CommitTree) startEvict(evictVersion uint32) { + if c.evictorRunning { + // eviction in progress + return + } + + if evictVersion <= c.lastEvictVersion { + // no new version to evict + return + } + + latest := c.latest.Load() + if latest == nil { + // nothing to evict + return + } + + c.logger.Debug("start eviction", "version", evictVersion, "depth", c.evictionDepth) + c.evictorRunning = true + go func() { + evictedCount := evictTraverse(latest, 0, c.evictionDepth, evictVersion) + c.logger.Debug("eviction completed", "version", evictVersion, "lastEvict", c.lastEvictVersion, "evictedNodes", evictedCount) + c.lastEvictVersion = evictVersion + c.evictorRunning = false + }() +} + +func (c *CommitTree) Commit() ([]byte, error) { + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if c.writeWal { + close(c.walChan) + } + + var hash []byte + savedVersion := c.store.SavedVersion() + stagedVersion := c.stagedVersion() + commitCtx := &commitContext{ + version: stagedVersion, + savedVersion: savedVersion, + } + if c.root == nil { + hash = emptyHash + } else { + // compute hash and assign node IDs + var err error + hash, err = commitTraverse(commitCtx, c.root, 0) + if err != nil { + return nil, err + } + } + + if c.writeWal { + // wait for WAL write to complete + err := <-c.walDone + if err != nil { + return nil, err + } + + err = c.store.WriteWALCommit(stagedVersion) + if err != nil { + return nil, err + } + + c.reinitWalProc() + } + + err := c.store.SaveRoot(stagedVersion, c.root, commitCtx.leafNodeIdx, commitCtx.branchNodeIdx) + if err != nil { + return nil, err + } + + c.store.MarkOrphans(stagedVersion, c.pendingOrphans) + c.pendingOrphans = nil + + // start eviction if needed + c.startEvict(savedVersion) + + // cache the committed tree as the latest version + c.latest.Store(c.root) + c.version++ + + return hash, nil +} + +func (c *CommitTree) Close() error { + if c.walChan != nil { + close(c.walChan) + } + //close(c.commitChan) + //return <-c.commitDone + return c.store.Close() +} + +type commitContext struct { + version uint32 + savedVersion uint32 + branchNodeIdx uint32 + leafNodeIdx uint32 +} + +func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []byte, err error) { + memNode := np.mem.Load() + if memNode == nil { + node, err := np.Resolve() + if err != nil { + return nil, err + } + return node.Hash(), nil + } + + if memNode.version != ctx.version { + return memNode.hash, nil + } + + var leftHash, rightHash []byte + if memNode.IsLeaf() { + ctx.leafNodeIdx++ + np.id = NewNodeID(true, uint64(ctx.version), ctx.leafNodeIdx) + } else { + // post-order traversal + leftHash, err = commitTraverse(ctx, memNode.left, depth+1) + if err != nil { + return nil, err + } + rightHash, err = commitTraverse(ctx, memNode.right, depth+1) + if err != nil { + return nil, err + } + + ctx.branchNodeIdx++ + np.id = NewNodeID(false, uint64(ctx.version), ctx.branchNodeIdx) + + } + + if memNode.hash != nil { + // not sure when we would encounter this but if the hash is already computed, just return it + return memNode.hash, nil + } + + return computeAndSetHash(memNode, leftHash, rightHash) +} + +func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uint32) (count int) { + memNode := np.mem.Load() + if memNode == nil { + return 0 + } + + // Evict nodes at or below the eviction depth + if memNode.version <= evictVersion && depth >= evictionDepth { + np.mem.Store(nil) + count = 1 + } + + if memNode.IsLeaf() { + return + } + + // Continue traversing to find nodes to evict + count += evictTraverse(memNode.left, depth+1, evictionDepth, evictVersion) + count += evictTraverse(memNode.right, depth+1, evictionDepth, evictVersion) + return +} diff --git a/iavl/compactor.go b/iavl/compactor.go new file mode 100644 index 000000000000..dbba338a972c --- /dev/null +++ b/iavl/compactor.go @@ -0,0 +1,353 @@ +package iavlx + +import ( + "errors" + "fmt" + "log/slog" +) + +type CompactOptions struct { + RetainCriteria RetainCriteria + CompactWAL bool + CompactedAt uint32 // version at which compaction is done +} + +type RetainCriteria func(createVersion, orphanVersion uint32) bool + +type Compactor struct { + logger *slog.Logger + + criteria RetainCriteria + compactWAL bool + + processedChangesets []*Changeset + treeStore *TreeStore + + originalKvLogPath string + files *ChangesetFiles + leavesWriter *StructWriter[LeafLayout] + branchesWriter *StructWriter[BranchLayout] + versionsWriter *StructWriter[VersionInfo] + kvlogWriter *KVLogWriter + + leafOffsetRemappings map[uint32]uint32 + keyCache map[string]uint32 + + // Running totals across all processed changesets + leafOrphanCount uint32 + branchOrphanCount uint32 + leafOrphanVersionTotal uint64 + branchOrphanVersionTotal uint64 +} + +func NewCompacter(logger *slog.Logger, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { + if reader.files == nil { + return nil, fmt.Errorf("changeset has no associated files, cannot compact a shared changeset reader which files set to nil") + } + files := reader.files + startingVersion := files.StartVersion() + lastCompactedAt := files.CompactedAtVersion() + if lastCompactedAt >= opts.CompactedAt { + return nil, fmt.Errorf("cannot compact changeset starting at version %d which was last compacted at %d to an earlier or same version %d", + startingVersion, lastCompactedAt, opts.CompactedAt) + } + + // if we're not compacting the WAL, we can reuse the existing KV log path + kvlogPath := reader.files.KVLogPath() + // if we're compacting the WAL, create a new KV log path + if opts.CompactWAL { + kvlogPath = "" + } + + newFiles, err := OpenChangesetFiles(files.TreeDir(), files.StartVersion(), opts.CompactedAt, kvlogPath) + if err != nil { + return nil, fmt.Errorf("failed to open new changeset files: %w", err) + } + + var kvlogWriter *KVLogWriter + // we only need a new KV log writer if we're compacting the WAL, otherwise it should be nil + if opts.CompactWAL { + kvlogWriter = NewKVDataWriter(newFiles.kvlogFile) + } + + c := &Compactor{ + logger: logger, + criteria: opts.RetainCriteria, + compactWAL: opts.CompactWAL, + treeStore: store, + files: newFiles, + originalKvLogPath: reader.files.KVLogPath(), + kvlogWriter: kvlogWriter, + leavesWriter: NewStructWriter[LeafLayout](newFiles.leavesFile), + branchesWriter: NewStructWriter[BranchLayout](newFiles.branchesFile), + versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile), + keyCache: make(map[string]uint32), + leafOffsetRemappings: make(map[uint32]uint32), + } + + // Process first changeset immediately + err = c.processChangeset(reader) + if err != nil { + return nil, fmt.Errorf("failed to process initial changeset: %w", err) + } + + return c, nil +} + +func (c *Compactor) processChangeset(reader *Changeset) error { + // Compute KV offset delta for non-CompactWAL mode + kvOffsetDelta := uint32(0) + if c.kvlogWriter != nil && !c.compactWAL { + kvOffsetDelta = uint32(c.kvlogWriter.Size()) + } + + versionsData := reader.versionsData + numVersions := versionsData.Count() + leavesData := reader.leavesData + branchesData := reader.branchesData + skippedBranches := 0 + + c.logger.Debug("processing changeset for compaction", "versions", numVersions) + for i := 0; i < numVersions; i++ { + c.logger.Debug("compacting version", "version", reader.info.StartVersion+uint32(i)) + verInfo := *versionsData.UnsafeItem(uint32(i)) // copy + newLeafStartIdx := uint32(0) + newLeafEndIdx := uint32(0) + leafStartOffset := verInfo.Leaves.StartOffset + leafCount := verInfo.Leaves.Count + newLeafStartOffset := uint32(c.leavesWriter.Count()) + newLeafCount := uint32(0) + // Iterate leaves + // For each leaf, check if it should be retained + for j := uint32(0); j < leafCount; j++ { + leaf := *leavesData.UnsafeItem(leafStartOffset + j) // copy + id := leaf.Id + retain := leaf.OrphanVersion == 0 || c.criteria(uint32(id.Version()), leaf.OrphanVersion) + if !retain { + continue + } + + if leaf.OrphanVersion != 0 { + c.leafOrphanCount++ + c.leafOrphanVersionTotal += uint64(leaf.OrphanVersion) + } + + if newLeafStartIdx == 0 { + newLeafStartIdx = id.Index() + } + newLeafEndIdx = id.Index() + newLeafCount++ + + if c.compactWAL { + k, v, err := reader.ReadKV(id, leaf.KeyOffset) + if err != nil { + return fmt.Errorf("failed to read KV for leaf %s: %w", id, err) + } + + offset, err := c.kvlogWriter.WriteKV(k, v) + if err != nil { + return fmt.Errorf("failed to write KV for leaf %s: %w", id, err) + } + + leaf.KeyOffset = offset + c.keyCache[string(k)] = offset + } else { + // When not compacting WAL, add offset delta + leaf.KeyOffset += kvOffsetDelta + } + + err := c.leavesWriter.Append(&leaf) + if err != nil { + return fmt.Errorf("failed to append leaf %s: %w", id, err) + } + + oldLeafFileIdx := leafStartOffset + j + c.leafOffsetRemappings[oldLeafFileIdx] = uint32(c.leavesWriter.Count()) - 1 + } + + newBranchStartIdx := uint32(0) + newBranchEndIdx := uint32(0) + branchStartOffset := verInfo.Branches.StartOffset + branchCount := verInfo.Branches.Count + newBranchStartOffset := uint32(c.branchesWriter.Count()) + newBranchCount := uint32(0) + for j := uint32(0); j < branchCount; j++ { + branch := *branchesData.UnsafeItem(branchStartOffset + j) // copy + id := branch.Id + retain := branch.OrphanVersion == 0 || c.criteria(uint32(id.Version()), branch.OrphanVersion) + if !retain { + skippedBranches++ + continue + } + + if branch.OrphanVersion != 0 { + c.branchOrphanCount++ + c.branchOrphanVersionTotal += uint64(branch.OrphanVersion) + } + + if newBranchStartIdx == 0 { + newBranchStartIdx = id.Index() + } + newBranchEndIdx = id.Index() + newBranchCount++ + + var err error + left := branch.Left + branch.Left, err = c.updateNodeRef(reader, left, skippedBranches) + if err != nil { + c.logger.Error("failed to update left ref", + "branchId", id, + "branchOrphanVersion", branch.OrphanVersion, + "leftRef", left) + return fmt.Errorf("failed to update left ref for branch %s: %w", id, err) + } + right := branch.Right + branch.Right, err = c.updateNodeRef(reader, right, skippedBranches) + if err != nil { + c.logger.Error("failed to update right ref", + "branchId", id, + "branchOrphanVersion", branch.OrphanVersion, + "rightRef", right) + return fmt.Errorf("failed to update right ref for branch %s: %w", id, err) + } + + if c.compactWAL { + k, err := reader.ReadK(id, branch.KeyOffset) + if err != nil { + return fmt.Errorf("failed to read key for branch %s: %w", id, err) + } + offset, ok := c.keyCache[string(k)] + if !ok { + offset, err = c.kvlogWriter.WriteK(k) + } + if err != nil { + return fmt.Errorf("failed to write key for branch %s: %w", id, err) + } + branch.KeyOffset = offset + } else { + // When not compacting WAL, add offset delta + branch.KeyOffset += kvOffsetDelta + } + + err = c.branchesWriter.Append(&branch) + if err != nil { + return fmt.Errorf("failed to append branch %s: %w", id, err) + } + } + + verInfo = VersionInfo{ + Leaves: NodeSetInfo{ + StartIndex: newLeafStartIdx, + EndIndex: newLeafEndIdx, + StartOffset: newLeafStartOffset, + Count: newLeafCount, + }, + Branches: NodeSetInfo{ + StartIndex: newBranchStartIdx, + EndIndex: newBranchEndIdx, + StartOffset: newBranchStartOffset, + Count: newBranchCount, + }, + RootID: verInfo.RootID, + } + + err := c.versionsWriter.Append(&verInfo) + if err != nil { + return fmt.Errorf("failed to append version info for version %d: %w", reader.info.StartVersion+uint32(i), err) + } + } + + // Track this changeset as processed + c.processedChangesets = append(c.processedChangesets, reader) + + return nil +} + +func (c *Compactor) AddChangeset(cs *Changeset) error { + // TODO: Support joining changesets when CompactWAL=false + // This requires copying the entire KV log and tracking cumulative offsets + if !c.compactWAL { + return fmt.Errorf("joining changesets is only supported when CompactWAL=true") + } + return c.processChangeset(cs) +} + +func (c *Compactor) Seal() (*Changeset, error) { + if len(c.processedChangesets) == 0 { + return nil, fmt.Errorf("no changesets processed") + } + + info := c.files.info + info.StartVersion = c.processedChangesets[0].info.StartVersion + info.EndVersion = c.processedChangesets[len(c.processedChangesets)-1].info.EndVersion + info.LeafOrphans = c.leafOrphanCount + info.BranchOrphans = c.branchOrphanCount + info.LeafOrphanVersionTotal = c.leafOrphanVersionTotal + info.BranchOrphanVersionTotal = c.branchOrphanVersionTotal + + errs := []error{ + c.leavesWriter.Flush(), + c.branchesWriter.Flush(), + c.versionsWriter.Flush(), + c.files.infoMmap.Flush(), + } + if c.kvlogWriter != nil { + errs = append(errs, c.kvlogWriter.Flush()) + } + if err := errors.Join(errs...); err != nil { + return nil, fmt.Errorf("failed to flush data during compaction seal: %w", err) + } + + cs := NewChangeset(c.treeStore) + err := cs.InitOwned(c.files) + if err != nil { + return nil, fmt.Errorf("failed to initialize sealed changeset: %w", err) + } + + return cs, nil +} + +func (c *Compactor) updateNodeRef(reader *Changeset, ref NodeRef, skipped int) (NodeRef, error) { + if ref.IsNodeID() { + return ref, nil + } + relPtr := ref.AsRelativePointer() + if relPtr.IsLeaf() { + oldOffset := relPtr.Offset() + newOffset, ok := c.leafOffsetRemappings[uint32(oldOffset)] + if !ok { + // Debug: look up the orphaned leaf + oldLeaf := reader.leavesData.UnsafeItem(uint32(oldOffset) - 1) + c.logger.Error("leaf remapping failed - orphaned leaf still referenced", + "leafOffset", oldOffset, + "leafId", oldLeaf.Id, + "leafOrphanVersion", oldLeaf.OrphanVersion, + "remappings", c.leafOffsetRemappings) + return 0, fmt.Errorf("failed to find remapping for leaf offset %d", oldOffset) + } + return NodeRef(NewNodeRelativePointer(true, int64(newOffset))), nil + } else { + // branch nodes we reduce by the number of skipped nodes + oldOffset := relPtr.Offset() + newOffset := oldOffset - int64(skipped) + return NodeRef(NewNodeRelativePointer(false, newOffset)), nil + } +} + +func (c *Compactor) Abort() error { + err := c.files.Close() + if err != nil { + return fmt.Errorf("failed to close compactor files during cleanup: %w", err) + } + return c.files.DeleteFiles(ChangesetDeleteArgs{ + SaveKVLogPath: c.originalKvLogPath, + }) +} + +func (c *Compactor) TotalBytes() int { + total := c.leavesWriter.Size() + c.branchesWriter.Size() + c.versionsWriter.Size() + if c.kvlogWriter != nil { + total += c.kvlogWriter.Size() + } + return total +} diff --git a/iavl/dot_graph.go b/iavl/dot_graph.go new file mode 100644 index 000000000000..bd35b077d7e6 --- /dev/null +++ b/iavl/dot_graph.go @@ -0,0 +1,95 @@ +package iavlx + +import ( + "fmt" + "io" +) + +func DebugTraverse(nodePtr *NodePointer, onNode func(node Node, parent Node, direction string) error) error { + if nodePtr == nil { + return nil + } + + var traverse func(np *NodePointer, parent Node, direction string) error + traverse = func(np *NodePointer, parent Node, direction string) error { + node, err := np.Resolve() + if err != nil { + return err + } + + if err := onNode(node, parent, direction); err != nil { + return err + } + + if node.IsLeaf() { + return nil + } + + err = traverse(node.Left(), node, "l") + if err != nil { + return err + } + err = traverse(node.Right(), node, "r") + if err != nil { + return err + } + return nil + } + + return traverse(nodePtr, nil, "") +} + +func RenderDotGraph(writer io.Writer, nodePtr *NodePointer) error { + _, err := fmt.Fprintln(writer, "digraph G {") + if err != nil { + return err + } + finishGraph := func() error { + _, err := fmt.Fprintln(writer, "}") + return err + } + if nodePtr == nil { + return finishGraph() + } + + err = DebugTraverse(nodePtr, func(node Node, parent Node, direction string) error { + key, err := node.Key() + if err != nil { + return err + } + + version := node.Version() + + label := fmt.Sprintf("ver: %d key:0x%x ", version, key) + if node.IsLeaf() { + value, err := node.Value() + if err != nil { + return err + } + + label += fmt.Sprintf("val:0x%X", value) + } else { + label += fmt.Sprintf("ht:%d sz:%d", node.Height(), node.Size()) + } + + nodeName := fmt.Sprintf("n%p", node) + + _, err = fmt.Fprintf(writer, "%s [label=\"%s\"];\n", nodeName, label) + if err != nil { + return err + } + if parent != nil { + parentName := fmt.Sprintf("n%p", parent) + _, err = fmt.Fprintf(writer, "%s -> %s [label=\"%s\"];\n", parentName, nodeName, direction) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return err + } + + return finishGraph() +} diff --git a/iavl/go.mod b/iavl/go.mod new file mode 100644 index 000000000000..39c58e1767e3 --- /dev/null +++ b/iavl/go.mod @@ -0,0 +1,48 @@ +module github.com/cosmos/cosmos-sdk/iavl + +go 1.24.0 + +require ( + cosmossdk.io/api v0.9.2 + cosmossdk.io/core v1.0.0 + cosmossdk.io/log v1.6.1 + github.com/alitto/pond/v2 v2.5.0 + github.com/cosmos/iavl v1.3.5 + github.com/edsrzf/mmap-go v1.2.0 + github.com/stretchr/testify v1.11.1 + github.com/tidwall/btree v1.8.1 + golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b + pgregory.net/rapid v1.2.0 +) + +require ( + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect + github.com/cosmos/gogoproto v1.7.0 // indirect + github.com/cosmos/ics23/go v0.10.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/dot v1.6.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect + google.golang.org/grpc v1.72.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/iavl/go.sum b/iavl/go.sum new file mode 100644 index 000000000000..bba58dacc443 --- /dev/null +++ b/iavl/go.sum @@ -0,0 +1,202 @@ +cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= +cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= +cosmossdk.io/core v1.0.0 h1:e7XBbISOytLBOXMVwpRPixThXqEkeLGlg8no/qpgS8U= +cosmossdk.io/core v1.0.0/go.mod h1:mKIp3RkoEmtqdEdFHxHwWAULRe+79gfdOvmArrLDbDc= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= +github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= +github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= +github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= +github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= +github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/cosmos/iavl v1.3.5 h1:wTDFbaa/L0FVUrwTlzMnjN3fphtKgWxgcZmTc45MZuA= +github.com/cosmos/iavl v1.3.5/go.mod h1:T6SfBcyhulVIY2G/ZtAtQm/QiJvsuhIos52V4dWYk88= +github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= +github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= +github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= +github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA= +golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/iavl/iterator.go b/iavl/iterator.go new file mode 100644 index 000000000000..111c0f9c9352 --- /dev/null +++ b/iavl/iterator.go @@ -0,0 +1,139 @@ +package iavlx + +import "bytes" + +type Iterator struct { + // domain of iteration, end is exclusive + start, end []byte + ascending bool + zeroCopy bool + + // cache the next key-value pair + key, value []byte + + err error + valid bool + + stack []*NodePointer +} + +func NewIterator(start, end []byte, ascending bool, root *NodePointer, zeroCopy bool) *Iterator { + iter := &Iterator{ + start: start, + end: end, + ascending: ascending, + valid: true, + zeroCopy: zeroCopy, + } + + if root != nil { + iter.stack = []*NodePointer{root} + } + + // cache the first key-value + iter.Next() + return iter +} + +func (iter *Iterator) Domain() ([]byte, []byte) { + return iter.start, iter.end +} + +// Valid implements dbm.Iterator. +func (iter *Iterator) Valid() bool { + return iter.valid +} + +// Error implements dbm.Iterator +func (iter *Iterator) Error() error { + return nil +} + +// Key implements dbm.Iterator +func (iter *Iterator) Key() []byte { + if !iter.zeroCopy { + return bytes.Clone(iter.key) + } + return iter.key +} + +// Value implements dbm.Iterator +func (iter *Iterator) Value() []byte { + if !iter.zeroCopy { + return bytes.Clone(iter.value) + } + return iter.value +} + +// Next implements dbm.Iterator +func (iter *Iterator) Next() { + if !iter.valid { + return + } + + for len(iter.stack) > 0 { + // pop node + nodePtr := iter.stack[len(iter.stack)-1] + iter.stack = iter.stack[:len(iter.stack)-1] + + node, err := nodePtr.Resolve() + if err != nil { + iter.fail(err) + return + } + + key, err := node.Key() + if err != nil { + iter.fail(err) + return + } + startCmp := bytes.Compare(iter.start, key) + afterStart := iter.start == nil || startCmp < 0 + beforeEnd := iter.end == nil || bytes.Compare(key, iter.end) < 0 + + if node.IsLeaf() { + startOrAfter := afterStart || startCmp == 0 + if startOrAfter && beforeEnd { + iter.key = key + value, err := node.Value() + if err != nil { + iter.fail(err) + return + } + iter.value = value + return + } + } else { + // push children to stack + if iter.ascending { + if beforeEnd { + iter.stack = append(iter.stack, node.Right()) + } + if afterStart { + iter.stack = append(iter.stack, node.Left()) + } + } else { + if afterStart { + iter.stack = append(iter.stack, node.Left()) + } + if beforeEnd { + iter.stack = append(iter.stack, node.Right()) + } + } + } + } + + iter.valid = false +} + +func (iter *Iterator) fail(err error) { + iter.valid = false + iter.err = err +} + +// Close implements dbm.Iterator +func (iter *Iterator) Close() error { + iter.valid = false + iter.stack = nil + return nil +} diff --git a/iavl/kvlog.go b/iavl/kvlog.go new file mode 100644 index 000000000000..db8999d2bda9 --- /dev/null +++ b/iavl/kvlog.go @@ -0,0 +1,51 @@ +package iavlx + +import ( + "encoding/binary" + "os" +) + +const ( + KVLogEntryTypeSet byte = iota + KVLogEntryTypeDelete + KVLogEntryTypeCommit + KVLogEntryTypeExtraK + KVLogEntryTypeExtraKV +) + +type KVLog struct { + *MmapFile +} + +func NewKVLog(file *os.File) (*KVLog, error) { + mmap, err := NewMmapFile(file) + if err != nil { + return nil, err + } + return &KVLog{ + MmapFile: mmap, + }, nil +} + +func (kvs *KVLog) UnsafeReadK(offset uint32) (key []byte, err error) { + bz, err := kvs.UnsafeSliceExact(int(offset), 4) + if err != nil { + return nil, err + } + lenKey := binary.LittleEndian.Uint32(bz) + + return kvs.UnsafeSliceExact(int(offset)+4, int(lenKey)) +} + +func (kvs *KVLog) ReadKV(offset uint32) (key, value []byte, err error) { + key, err = kvs.UnsafeReadK(offset) + if err != nil { + return nil, nil, err + } + + value, err = kvs.UnsafeReadK(offset + 4 + uint32(len(key))) + if err != nil { + return nil, nil, err + } + return key, value, nil +} diff --git a/iavl/kvlog_writer.go b/iavl/kvlog_writer.go new file mode 100644 index 000000000000..da1f6298e6ac --- /dev/null +++ b/iavl/kvlog_writer.go @@ -0,0 +1,115 @@ +package iavlx + +import ( + "encoding/binary" + "fmt" + "math" + "os" +) + +type KVLogWriter struct { + *FileWriter +} + +func NewKVDataWriter(file *os.File) *KVLogWriter { + fw := NewFileWriter(file) + return &KVLogWriter{ + FileWriter: fw, + } + +} + +func (kvs *KVLogWriter) WriteK(key []byte) (offset uint32, err error) { + _, err = kvs.Write([]byte{KVLogEntryTypeExtraK}) + if err != nil { + return + } + + return kvs.writeLenPrefixedBytes(key) +} + +func (kvs *KVLogWriter) WriteKV(key, value []byte) (offset uint32, err error) { + _, err = kvs.Write([]byte{KVLogEntryTypeExtraKV}) + if err != nil { + return + } + + offset, err = kvs.writeLenPrefixedBytes(key) + if err != nil { + return 0, err + } + _, err = kvs.writeLenPrefixedBytes(value) + return offset, err +} + +func (kvs *KVLogWriter) WriteUpdates(updates []KVUpdate) error { + for _, update := range updates { + if deleteKey := update.DeleteKey; deleteKey != nil { + _, err := kvs.Write([]byte{KVLogEntryTypeDelete}) + if err != nil { + return err + } + _, err = kvs.writeLenPrefixedBytes(deleteKey) + if err != nil { + return err + } + } else if memNode := update.SetNode; memNode != nil { + _, err := kvs.Write([]byte{KVLogEntryTypeSet}) + if err != nil { + return err + } + offset, err := kvs.writeLenPrefixedBytes(memNode.key) + if err != nil { + return err + } + memNode.keyOffset = offset + + _, err = kvs.writeLenPrefixedBytes(memNode.value) + if err != nil { + return err + } + } else { + return fmt.Errorf("invalid update: neither SetNode nor DeleteKey is set") + } + } + return nil +} + +func (kvs *KVLogWriter) WriteCommit(version uint32) error { + _, err := kvs.Write([]byte{KVLogEntryTypeCommit}) + if err != nil { + return err + } + + return kvs.writeLEU32(version) +} + +func (kvs *KVLogWriter) writeLenPrefixedBytes(key []byte) (offset uint32, err error) { + lenKey := len(key) + if lenKey > math.MaxUint32 { + return 0, fmt.Errorf("key too large: %d bytes", lenKey) + } + + offset = uint32(kvs.Size()) + + // write little endian uint32 length prefix + err = kvs.writeLEU32(uint32(lenKey)) + if err != nil { + return + } + + // write key bytes + _, err = kvs.Write(key) + if err != nil { + return + } + + return offset, nil +} + +func (kvs *KVLogWriter) writeLEU32(x uint32) error { + var buf [4]byte + binary.LittleEndian.PutUint32(buf[:], x) + _, err := kvs.Write(buf[:]) + return err +} diff --git a/iavl/leaf_layout.go b/iavl/leaf_layout.go new file mode 100644 index 000000000000..3f50cdb65423 --- /dev/null +++ b/iavl/leaf_layout.go @@ -0,0 +1,27 @@ +package iavlx + +import ( + "fmt" + "unsafe" +) + +func init() { + if unsafe.Sizeof(LeafLayout{}) != SizeLeaf { + panic(fmt.Sprintf("invalid LeafLayout size: got %d, want %d", unsafe.Sizeof(LeafLayout{}), SizeLeaf)) + } +} + +const ( + SizeLeaf = 48 +) + +type LeafLayout struct { + Id NodeID + KeyOffset uint32 + OrphanVersion uint32 // TODO 5 bytes? + Hash [32]byte +} + +func (l LeafLayout) ID() NodeID { + return l.Id +} diff --git a/iavl/leaf_persisted.go b/iavl/leaf_persisted.go new file mode 100644 index 000000000000..095678737882 --- /dev/null +++ b/iavl/leaf_persisted.go @@ -0,0 +1,88 @@ +package iavlx + +import ( + "bytes" + "fmt" +) + +type LeafPersisted struct { + store *Changeset + selfIdx uint32 + layout LeafLayout +} + +func (node *LeafPersisted) ID() NodeID { + return node.layout.Id +} + +func (node *LeafPersisted) Height() uint8 { + return 0 +} + +func (node *LeafPersisted) IsLeaf() bool { + return true +} + +func (node *LeafPersisted) Size() int64 { + return 1 +} + +func (node *LeafPersisted) Version() uint32 { + return uint32(node.layout.Id.Version()) +} + +func (node *LeafPersisted) Key() ([]byte, error) { + return node.store.ReadK(node.layout.Id, node.layout.KeyOffset) +} + +func (node *LeafPersisted) Value() ([]byte, error) { + _, v, err := node.store.ReadKV(node.layout.Id, node.layout.KeyOffset) + return v, err +} + +func (node *LeafPersisted) Left() *NodePointer { + return nil +} + +func (node *LeafPersisted) Right() *NodePointer { + return nil +} + +func (node *LeafPersisted) Hash() []byte { + return node.layout.Hash[:] +} + +func (node *LeafPersisted) SafeHash() []byte { + // TODO how do we make this safe? + return node.layout.Hash[:] +} + +func (node *LeafPersisted) MutateBranch(uint32) (*MemNode, error) { + return nil, fmt.Errorf("leaf nodes should not get mutated this way") +} + +func (node *LeafPersisted) Get(key []byte) (value []byte, index int64, err error) { + nodeKey, err := node.Key() + if err != nil { + return nil, 0, err + } + switch bytes.Compare(nodeKey, key) { + case -1: + return nil, 1, nil + case 1: + return nil, 0, nil + default: + value, err := node.Value() + if err != nil { + return nil, 0, err + } + return value, 0, nil + } +} + +func (node *LeafPersisted) String() string { + //TODO implement me + panic("implement me") +} + +var _ Node = (*LeafPersisted)(nil) diff --git a/iavl/mem_node.go b/iavl/mem_node.go new file mode 100644 index 000000000000..a9582625e7aa --- /dev/null +++ b/iavl/mem_node.go @@ -0,0 +1,117 @@ +package iavlx + +import ( + "bytes" + "fmt" +) + +type MemNode struct { + height uint8 + size int64 + version uint32 + key []byte + value []byte + left *NodePointer + right *NodePointer + hash []byte + nodeId NodeID // ID of this node, 0 if not yet assigned + keyOffset uint32 +} + +func (node *MemNode) ID() NodeID { + return node.nodeId +} + +func (node *MemNode) Height() uint8 { + return node.height +} + +func (node *MemNode) Size() int64 { + return node.size +} + +func (node *MemNode) Version() uint32 { + return node.version +} + +func (node *MemNode) Key() ([]byte, error) { + return node.key, nil +} + +func (node *MemNode) Value() ([]byte, error) { + return node.value, nil +} + +func (node *MemNode) Left() *NodePointer { + return node.left +} + +func (node *MemNode) Right() *NodePointer { + return node.right +} + +func (node *MemNode) Hash() []byte { + return node.hash +} + +func (node *MemNode) SafeHash() []byte { + // TODO what needs to be safe?? + return node.hash +} + +func (node *MemNode) MutateBranch(version uint32) (*MemNode, error) { + n := *node + n.version = version + n.hash = nil + return &n, nil +} + +func (node *MemNode) Get(key []byte) (value []byte, index int64, err error) { + if node.IsLeaf() { + switch bytes.Compare(node.key, key) { + case -1: + return nil, 1, nil + case 1: + return nil, 0, nil + default: + return node.value, 0, nil + } + } + + if bytes.Compare(key, node.key) < 0 { + leftNode, err := node.left.Resolve() + if err != nil { + return nil, 0, err + } + + return leftNode.Get(key) + } + + rightNode, err := node.right.Resolve() + if err != nil { + return nil, 0, err + } + + value, index, err = rightNode.Get(key) + if err != nil { + return nil, 0, err + } + + index += node.size - rightNode.Size() + return value, index, nil +} + +func (node *MemNode) IsLeaf() bool { + return node.height == 0 +} + +func (node *MemNode) String() string { + if node.IsLeaf() { + return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, value:%x}", node.key, node.version, node.size, node.value) + } else { + + return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, height:%d, left:%s, right:%s}", node.key, node.version, node.size, node.height, node.left, node.right) + } +} + +var _ Node = &MemNode{} diff --git a/iavl/mmap.go b/iavl/mmap.go new file mode 100644 index 000000000000..e091d31af329 --- /dev/null +++ b/iavl/mmap.go @@ -0,0 +1,87 @@ +package iavlx + +import ( + "fmt" + "io" + "os" + + "github.com/edsrzf/mmap-go" +) + +type MmapFile struct { + handle mmap.MMap +} + +func NewMmapFile(file *os.File) (*MmapFile, error) { + // Check file size + fi, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, fmt.Errorf("failed to stat file: %w", err) + } + + res := &MmapFile{} + + // Empty files are valid - just don't mmap them + if fi.Size() == 0 { + return res, nil + } + + // maybe we can make read/write configurable? not sure if the OS optimizes read-only mapping + handle, err := mmap.Map(file, mmap.RDWR, 0) + if err != nil { + _ = file.Close() + return nil, fmt.Errorf("failed to mmap file: %w", err) + } + + res.handle = handle + return res, nil +} + +func (m *MmapFile) UnsafeSliceVar(offset, maxSize int) (int, []byte, error) { + if offset >= len(m.handle) { + return 0, nil, fmt.Errorf("trying to read beyond mapped data: %d >= %d", offset, len(m.handle)) + } + if offset+maxSize > len(m.handle) { + maxSize = len(m.handle) - offset + } + data := m.handle[offset : offset+maxSize] + // make a copy of the data to avoid data being changed after remap + return maxSize, data, nil +} + +func (m *MmapFile) UnsafeSliceExact(offset, size int) ([]byte, error) { + if offset+size > len(m.handle) { + return nil, fmt.Errorf("trying to read beyond mapped data: %d + %d >= %d", offset, size, len(m.handle)) + } + bz := m.handle[offset : offset+size] + return bz, nil +} + +func (m *MmapFile) Data() []byte { + return m.handle +} + +func (m *MmapFile) Flush() error { + if m.handle != nil { + if err := m.handle.Flush(); err != nil { + return fmt.Errorf("failed to flush mmap: %w", err) + } + } + return nil +} + +func (m *MmapFile) Close() error { + if m.handle != nil { + handle := m.handle + m.handle = nil + return handle.Unmap() + } + return nil +} + +func (m *MmapFile) TotalBytes() int { + return len(m.handle) +} + +var _ io.Closer = &MmapFile{} diff --git a/iavl/multi_tree.go b/iavl/multi_tree.go new file mode 100644 index 000000000000..c22f564afae1 --- /dev/null +++ b/iavl/multi_tree.go @@ -0,0 +1,14 @@ +package iavlx + +type MultiTree struct { + trees []*Tree // always ordered by tree name + treesByName map[string]int // index of the trees by name +} + +func (t *MultiTree) TreeByName(name string) *Tree { + idx, ok := t.treesByName[name] + if !ok { + return nil + } + return t.trees[idx] +} diff --git a/iavl/node.go b/iavl/node.go new file mode 100644 index 000000000000..721d2a6f22b8 --- /dev/null +++ b/iavl/node.go @@ -0,0 +1,21 @@ +package iavlx + +import "fmt" + +type Node interface { + ID() NodeID + Height() uint8 + IsLeaf() bool + Size() int64 + Version() uint32 + Key() ([]byte, error) + Value() ([]byte, error) + Left() *NodePointer + Right() *NodePointer + Hash() []byte + SafeHash() []byte + MutateBranch(version uint32) (*MemNode, error) + Get(key []byte) (value []byte, index int64, err error) + + fmt.Stringer +} diff --git a/iavl/node_hash.go b/iavl/node_hash.go new file mode 100644 index 000000000000..0ce5f12317e1 --- /dev/null +++ b/iavl/node_hash.go @@ -0,0 +1,98 @@ +package iavlx + +import ( + "crypto/sha256" + "encoding/binary" + "fmt" + "io" +) + +func computeAndSetHash(node *MemNode, leftHash, rightHash []byte) ([]byte, error) { + h, err := computeHash(node, leftHash, rightHash) + if err != nil { + return nil, err + } + node.hash = h + + return h, nil +} + +func computeHash(node Node, leftHash, rightHash []byte) ([]byte, error) { + hasher := sha256.New() + if err := writeHashBytes(node, leftHash, rightHash, hasher); err != nil { + return nil, err + } + return hasher.Sum(nil), nil +} + +var ( + emptyHash = sha256.New().Sum(nil) +) + +// Writes the node's hash to the given `io.Writer`. This function recursively calls +// children to update hashes. +func writeHashBytes(node Node, leftHash, rightHash []byte, w io.Writer) error { + var ( + n int + buf [binary.MaxVarintLen64]byte + ) + + n = binary.PutVarint(buf[:], int64(node.Height())) + if _, err := w.Write(buf[0:n]); err != nil { + return fmt.Errorf("writing height, %w", err) + } + n = binary.PutVarint(buf[:], node.Size()) + if _, err := w.Write(buf[0:n]); err != nil { + return fmt.Errorf("writing size, %w", err) + } + n = binary.PutVarint(buf[:], int64(node.Version())) + if _, err := w.Write(buf[0:n]); err != nil { + return fmt.Errorf("writing version, %w", err) + } + + // Key is not written for inner nodes, unlike writeBytes. + + if node.IsLeaf() { + key, err := node.Key() + if err != nil { + return fmt.Errorf("getting key, %w", err) + } + + if err := encodeVarintPrefixedBytes(w, key); err != nil { + return fmt.Errorf("writing key, %w", err) + } + + value, err := node.Value() + if err != nil { + return fmt.Errorf("getting value, %w", err) + } + + // Indirection needed to provide proofs without values. + // (e.g. ProofLeafNode.ValueHash) + valueHash := sha256.Sum256(value) + if err := encodeVarintPrefixedBytes(w, valueHash[:]); err != nil { + return fmt.Errorf("writing value, %w", err) + } + } else { + if err := encodeVarintPrefixedBytes(w, leftHash); err != nil { + return fmt.Errorf("writing left hash, %w", err) + } + if err := encodeVarintPrefixedBytes(w, rightHash); err != nil { + return fmt.Errorf("writing right hash, %w", err) + } + } + + return nil +} + +// encodeVarintPrefixedBytes writes a varint length-prefixed byte slice to the writer, +// it's used for hash computation, must be compactible with the official IAVL implementation. +func encodeVarintPrefixedBytes(w io.Writer, bz []byte) error { + var buf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(buf[:], uint64(len(bz))) + if _, err := w.Write(buf[0:n]); err != nil { + return err + } + _, err := w.Write(bz) + return err +} diff --git a/iavl/node_id.go b/iavl/node_id.go new file mode 100644 index 000000000000..657e698e76ab --- /dev/null +++ b/iavl/node_id.go @@ -0,0 +1,121 @@ +package iavlx + +import "fmt" + +// bit 63 indicates whether this is a node ID (0) or relative pointer (1) +// a valid NodeID should always have bit 63 as 0 +// bit 62 indicates whether this is a leaf (1) or branch (0) +// bits 61-23 (39 bits) are for version +// bits 22-0 (23 bits) are for index +type NodeID uint64 + +func NewNodeID(isLeaf bool, version uint64, index uint32) NodeID { + // check 40 bits for version and 23 bits for index + if version >= 0x10000000000 { + panic("version too large for NodeID") + } + if index >= 0x800000 { + panic("index too large for NodeID") + } + var id uint64 + if isLeaf { + id |= 1 << 62 + } + id |= (version & 0x7FFFFFFFFF) << 23 + id |= uint64(index & 0x7FFFFF) + return NodeID(id) +} + +func (id NodeID) IsLeaf() bool { + // check if second highest bit is set + return id&(1<<62) != 0 +} + +func (id NodeID) Version() uint64 { + return (uint64(id) >> 23) & 0x7FFFFF +} + +func (id NodeID) Index() uint32 { + return uint32(id & 0x7FFFFF) +} + +func (id NodeID) String() string { + return fmt.Sprintf("NodeID{leaf:%t, version:%d, index:%d}", id.IsLeaf(), id.Version(), id.Index()) +} + +// bit 63 indicates whether this is a node ID (0) or relative pointer (1) +type NodeRef uint64 + +func (ref NodeRef) IsRelativePointer() bool { + return ref&(1<<63) != 0 +} + +func (ref NodeRef) IsNodeID() bool { + return ref&(1<<63) == 0 +} + +func (ref NodeRef) IsLeaf() bool { + return ref&(1<<62) != 0 +} + +func (ref NodeRef) AsNodeID() NodeID { + return NodeID(ref) +} + +func (ref NodeRef) AsRelativePointer() NodeRelativePointer { + return NodeRelativePointer(ref &^ (1 << 63)) +} + +func (ref NodeRef) String() string { + if ref.IsNodeID() { + return fmt.Sprintf("NodeRef(%s)", ref.AsNodeID()) + } else { + return fmt.Sprintf("NodeRef(%s)", ref.AsRelativePointer()) + } +} + +// bit 63 indicates whether this is a node ID (0) or relative pointer (1) +// a valid NodeRelativePointer should always have bit 63 as 0 +// bit 62 indicates whether this is a leaf (1) or branch (0) +// bits 61-0 (62 bits) are for signed offset +type NodeRelativePointer uint64 + +func NewNodeRelativePointer(isLeaf bool, offset int64) NodeRelativePointer { + // check offset fits in 61 bits signed + if offset < -0x1FFFFFFFFFFFFFFF || offset > 0x1FFFFFFFFFFFFFFF { + panic("offset too large for NodeRelativePointer") + } + var ptr uint64 + ptr |= 1 << 63 // set bit 63 to indicate relative pointer + if isLeaf { + ptr |= 1 << 62 + } + // Store absolute value of offset in bits 60-0 + // Use bit 61 as sign bit (1 = negative) + if offset < 0 { + ptr |= 1 << 61 // set sign bit + ptr |= uint64(-offset) & 0x1FFFFFFFFFFFFFFF // store absolute value in lower 61 bits + } else { + ptr |= uint64(offset) & 0x1FFFFFFFFFFFFFFF // store value in lower 61 bits + } + return NodeRelativePointer(ptr) +} + +func (ptr NodeRelativePointer) IsLeaf() bool { + // check if second highest bit is set + return ptr&(1<<62) != 0 +} + +func (ptr NodeRelativePointer) Offset() int64 { + // Extract the absolute value from lower 61 bits + offset := int64(ptr & 0x1FFFFFFFFFFFFFFF) + // if bit 61 is set, it's negative + if ptr&(1<<61) != 0 { + offset = -offset + } + return offset +} + +func (ptr NodeRelativePointer) String() string { + return fmt.Sprintf("NodeRelativePointer{leaf:%t, offset:%d}", ptr.IsLeaf(), ptr.Offset()) +} diff --git a/iavl/node_pointer.go b/iavl/node_pointer.go new file mode 100644 index 000000000000..26cc1c568fef --- /dev/null +++ b/iavl/node_pointer.go @@ -0,0 +1,31 @@ +package iavlx + +import ( + "fmt" + "sync/atomic" +) + +type NodePointer struct { + mem atomic.Pointer[MemNode] + store *Changeset + fileIdx uint32 // absolute index in file, 1-based, zero means we don't have an offset + id NodeID +} + +func NewNodePointer(memNode *MemNode) *NodePointer { + n := &NodePointer{} + n.mem.Store(memNode) + return n +} + +func (p *NodePointer) Resolve() (Node, error) { + mem := p.mem.Load() + if mem != nil { + return mem, nil + } + return p.store.Resolve(p.id, p.fileIdx) +} + +func (p *NodePointer) String() string { + return fmt.Sprintf("NodePointer{id: %s, fileIdx: %d}", p.id.String(), p.fileIdx) +} diff --git a/iavl/node_update.go b/iavl/node_update.go new file mode 100644 index 000000000000..db8d7837c7ef --- /dev/null +++ b/iavl/node_update.go @@ -0,0 +1,357 @@ +package iavlx + +import "bytes" + +// setRecursive do set operation. +// it always do modification and return new `MemNode`, even if the value is the same. +// also returns if it's an update or insertion, if update, the tree height and balance is not changed. +func setRecursive(nodePtr *NodePointer, leafNode *MemNode, ctx *MutationContext) (*NodePointer, bool, error) { + if nodePtr == nil { + return NewNodePointer(leafNode), true, nil + } + + node, err := nodePtr.Resolve() + if err != nil { + return nil, false, err + } + + nodeKey, err := node.Key() + if err != nil { + return nil, false, err + } + if node.IsLeaf() { + leafNodePtr := NewNodePointer(leafNode) + cmp := bytes.Compare(leafNode.key, nodeKey) + if cmp == 0 { + ctx.AddOrphan(nodePtr.id) + return leafNodePtr, true, nil + } + n := &MemNode{ + height: 1, + size: 2, + version: ctx.Version, + } + switch cmp { + case -1: + n.left = leafNodePtr + n.right = nodePtr + n.key = nodeKey + // n._keyRef = node + case 1: + n.left = nodePtr + n.right = leafNodePtr + n.key = leafNode.key + // n._keyRef = leafNode + default: + panic("unreachable") + } + return NewNodePointer(n), false, nil + } else { + var ( + newChildPtr *NodePointer + newNode *MemNode + updated bool + err error + ) + if bytes.Compare(leafNode.key, nodeKey) == -1 { + newChildPtr, updated, err = setRecursive(node.Left(), leafNode, ctx) + if err != nil { + return nil, false, err + } + newNode, err = ctx.MutateBranch(node) + if err != nil { + return nil, false, err + } + newNode.left = newChildPtr + } else { + newChildPtr, updated, err = setRecursive(node.Right(), leafNode, ctx) + if err != nil { + return nil, false, err + } + newNode, err = ctx.MutateBranch(node) + if err != nil { + return nil, false, err + } + newNode.right = newChildPtr + } + + if !updated { + err = newNode.updateHeightSize() + if err != nil { + return nil, false, err + } + + newNode, err = newNode.reBalance(ctx) + if err != nil { + return nil, false, err + } + } + + return NewNodePointer(newNode), updated, nil + } +} + +type newKeyWrapper struct { + key []byte + //keyRef keyRefLink +} + +// removeRecursive returns: +// - (nil, origNode, nil) -> nothing changed in subtree +// - (value, nil, nil) -> leaf node is removed +// - (value, new node, newKey) -> subtree changed +func removeRecursive(nodePtr *NodePointer, key []byte, ctx *MutationContext) (value []byte, newNodePtr *NodePointer, newKey *newKeyWrapper, err error) { + if nodePtr == nil { + return nil, nil, nil, nil + } + + node, err := nodePtr.Resolve() + if err != nil { + return nil, nil, nil, err + } + + nodeKey, err := node.Key() + if err != nil { + return nil, nil, nil, err + } + + if node.IsLeaf() { + if bytes.Equal(nodeKey, key) { + ctx.AddOrphan(nodePtr.id) + value, err := node.Value() + return value, nil, nil, err + } + return nil, nodePtr, nil, nil + } + + if bytes.Compare(key, nodeKey) == -1 { + value, newLeft, newKey, err := removeRecursive(node.Left(), key, ctx) + if err != nil { + return nil, nil, nil, err + } + + if value == nil { + return nil, nodePtr, nil, nil + } + + if newLeft == nil { + ctx.AddOrphan(nodePtr.id) + return value, node.Right(), &newKeyWrapper{ + key: nodeKey, + //keyRef: nodePtr, + }, nil + } + + newNode, err := ctx.MutateBranch(node) + if err != nil { + return nil, nil, nil, err + } + newNode.left = newLeft + err = newNode.updateHeightSize() + if err != nil { + return nil, nil, nil, err + } + newNode, err = newNode.reBalance(ctx) + if err != nil { + return nil, nil, nil, err + } + + return value, NewNodePointer(newNode), newKey, nil + } + + value, newRight, newKey, err := removeRecursive(node.Right(), key, ctx) + if err != nil { + return nil, nil, nil, err + } + + if value == nil { + return nil, nodePtr, nil, nil + } + + if newRight == nil { + ctx.AddOrphan(nodePtr.id) + return value, node.Left(), nil, nil + } + + newNode, err := ctx.MutateBranch(node) + if err != nil { + return nil, nil, nil, err + } + + newNode.right = newRight + if newKey != nil { + newNode.key = newKey.key + //newNode._keyRef = newKey.keyRef + } + + err = newNode.updateHeightSize() + if err != nil { + return nil, nil, nil, err + } + + newNode, err = newNode.reBalance(ctx) + if err != nil { + return nil, nil, nil, err + } + + return value, NewNodePointer(newNode), nil, nil +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) updateHeightSize() error { + leftNode, err := node.left.Resolve() + if err != nil { + return err + } + + rightNode, err := node.right.Resolve() + if err != nil { + return err + } + + node.height = maxUint8(leftNode.Height(), rightNode.Height()) + 1 + node.size = leftNode.Size() + rightNode.Size() + return nil +} + +func maxUint8(a, b uint8) uint8 { + if a > b { + return a + } + return b +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) reBalance(ctx *MutationContext) (*MemNode, error) { + balance, err := calcBalance(node) + if err != nil { + return nil, err + } + switch { + case balance > 1: + left, err := node.left.Resolve() + if err != nil { + return nil, err + } + + leftBalance, err := calcBalance(left) + if err != nil { + return nil, err + } + + if leftBalance >= 0 { + // left left + return node.rotateRight(ctx) + } + + // left right + newLeft, err := ctx.MutateBranch(left) + if err != nil { + return nil, err + } + newLeft, err = newLeft.rotateLeft(ctx) + if err != nil { + return nil, err + } + node.left = NewNodePointer(newLeft) + return node.rotateRight(ctx) + case balance < -1: + right, err := node.right.Resolve() + if err != nil { + return nil, err + } + + rightBalance, err := calcBalance(right) + if err != nil { + return nil, err + } + + if rightBalance <= 0 { + // right right + return node.rotateLeft(ctx) + } + + // right left + newRight, err := ctx.MutateBranch(right) + if err != nil { + return nil, err + } + newRight, err = newRight.rotateRight(ctx) + node.right = NewNodePointer(newRight) + return node.rotateLeft(ctx) + default: + // nothing changed + return node, err + } +} + +func calcBalance(node Node) (int, error) { + leftNode, err := node.Left().Resolve() + if err != nil { + return 0, err + } + + rightNode, err := node.Right().Resolve() + if err != nil { + return 0, err + } + + return int(leftNode.Height()) - int(rightNode.Height()), nil +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) rotateRight(ctx *MutationContext) (*MemNode, error) { + left, err := node.left.Resolve() + if err != nil { + return nil, err + } + newSelf, err := ctx.MutateBranch(left) + if err != nil { + return nil, err + } + node.left = left.Right() + newSelf.right = NewNodePointer(node) + + err = node.updateHeightSize() + if err != nil { + return nil, err + } + err = newSelf.updateHeightSize() + if err != nil { + return nil, err + } + + return newSelf, nil +} + +// IMPORTANT: nodes called with this method must be new or copies first. +// Code reviewers should use find usages to ensure that all callers follow this rule! +func (node *MemNode) rotateLeft(ctx *MutationContext) (*MemNode, error) { + right, err := node.right.Resolve() + if err != nil { + return nil, err + } + + newSelf, err := ctx.MutateBranch(right) + if err != nil { + return nil, err + } + + node.right = right.Left() + newSelf.left = NewNodePointer(node) + + err = node.updateHeightSize() + if err != nil { + return nil, err + } + + err = newSelf.updateHeightSize() + if err != nil { + return nil, err + } + + return newSelf, nil +} diff --git a/iavl/options.go b/iavl/options.go new file mode 100644 index 000000000000..ee780336957f --- /dev/null +++ b/iavl/options.go @@ -0,0 +1,95 @@ +package iavlx + +type Options struct { + // ZeroCopy attempts to reduce copying of buffers, but this isn't really implemented yet and may not even be safe to implement. + ZeroCopy bool `json:"zero_copy"` + + // EvictDepth defines the depth at which eviction occurs. 255 means no eviction. + EvictDepth uint8 `json:"evict_depth"` + + // WriteWAL enables write-ahead logging for durability + WriteWAL bool `json:"write_wal"` + + // WalSyncBuffer controls WAL sync behavior: -1 = blocking fsync, 0 = async sync immediately (buffer=1), >0 = buffer size + WalSyncBuffer int `json:"wal_sync_buffer"` + + // CompactWAL determines if KV data is copied during compaction (true) or reused (false) + CompactWAL bool `json:"compact_wal"` + // DisableCompaction turns off background compaction entirely + DisableCompaction bool `json:"disable_compaction"` + + // CompactionOrphanRatio is the orphan/total ratio (0-1) that triggers compaction + CompactionOrphanRatio float64 `json:"compaction_orphan_ratio"` + // CompactionOrphanAge is the average age of orphans (in versions) at which compaction is triggered + CompactionOrphanAge uint32 `json:"compaction_orphan_age"` + + // RetainVersions is the number of recent versions to keep uncompacted + RetainVersions uint32 `json:"retain_versions"` + // MinCompactionSeconds is the minimum interval between compaction runs + MinCompactionSeconds uint32 `json:"min_compaction_seconds"` + // ChangesetMaxTarget is the maximum size of a changeset file when batching new versions + ChangesetMaxTarget uint32 `json:"changeset_max_target"` + // CompactionMaxTarget is the maximum size when joining/compacting old changesets + CompactionMaxTarget uint32 `json:"compaction_max_target"` + // CompactAfterVersions is the number of versions after which a full compaction is forced whenever there are orphans + CompactAfterVersions uint32 `json:"compact_after_versions"` + // ReaderUpdateInterval controls how often we create new mmap readers during batching (in versions) + // Setting to 0 means create reader every version (high mmap churn) + // Higher values reduce mmap overhead but delay when data becomes readable + ReaderUpdateInterval uint32 `json:"reader_update_interval"` +} + +// GetWalSyncBufferSize returns the actual buffer size to use (handling 0 = 1 case) +func (o Options) GetWalSyncBufferSize() int { + if o.WalSyncBuffer == 0 { + return 1 // 0 means async sync immediately with buffer of 1 + } + return o.WalSyncBuffer +} + +// GetCompactionOrphanAge returns the orphan age threshold with default +func (o Options) GetCompactionOrphanAge() uint32 { + if o.CompactionOrphanAge == 0 { + return 10 // Default to 10 versions + } + return o.CompactionOrphanAge +} + +// GetCompactionOrphanRatio returns the orphan ratio threshold with default +func (o Options) GetCompactionOrphanRatio() float64 { + if o.CompactionOrphanRatio <= 0 { + return 0.6 // Default to 60% orphans + } + return o.CompactionOrphanRatio +} + +// GetChangesetMaxTarget returns the max changeset size with default +func (o Options) GetChangesetMaxTarget() uint64 { + if o.ChangesetMaxTarget == 0 { + return 128 * 1024 * 1024 // 128MB default for changesets + } + return uint64(o.ChangesetMaxTarget) +} + +// GetCompactionMaxTarget returns the max size for compaction with default +func (o Options) GetCompactionMaxTarget() uint64 { + if o.CompactionMaxTarget == 0 { + return 1024 * 1024 * 1024 // 1GB default for compaction + } + return uint64(o.CompactionMaxTarget) +} + +func (o Options) GetCompactAfterVersions() uint32 { + if o.CompactAfterVersions == 0 { + return 500 // default to 500 versions + } + return o.CompactAfterVersions +} + +// GetReaderUpdateInterval returns the interval for creating readers with default +func (o Options) GetReaderUpdateInterval() uint32 { + if o.ReaderUpdateInterval == 0 { + return 100 // Default to updating reader every 100 versions + } + return o.ReaderUpdateInterval +} diff --git a/iavl/reader.go b/iavl/reader.go new file mode 100644 index 000000000000..e130b4b53e83 --- /dev/null +++ b/iavl/reader.go @@ -0,0 +1,135 @@ +package iavlx + +import ( + "fmt" + "os" + "unsafe" +) + +// check little endian at init time +func init() { + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) + + if buf != [2]byte{0xCD, 0xAB} { + panic("native byte order is not little endian, please build without nativebyteorder") + } +} + +type StructMmap[T any] struct { + items []T + file *MmapFile + size int +} + +func NewStructReader[T any](file *os.File) (*StructMmap[T], error) { + mmap, err := NewMmapFile(file) + if err != nil { + return nil, err + } + + var zero T + df := &StructMmap[T]{ + file: mmap, + size: int(unsafe.Sizeof(zero)), + } + + buf := mmap.Data() + p := unsafe.Pointer(unsafe.SliceData(buf)) + align := unsafe.Alignof(zero) + if uintptr(p)%align != 0 { + return nil, fmt.Errorf("input buffer is not aligned: %p", p) + } + + size := df.size + if len(buf)%size != 0 { + return nil, fmt.Errorf("input buffer size is not a multiple of struct size: %d %% %d != 0", len(buf), size) + } + data := unsafe.Slice((*T)(p), len(buf)/size) + df.items = data + + return df, nil +} + +func (df *StructMmap[T]) UnsafeItem(i uint32) *T { + return &df.items[i] +} + +func (df *StructMmap[T]) Count() int { + return len(df.items) +} + +func (df *StructMmap[T]) Flush() error { + return df.file.Flush() +} + +func (df *StructMmap[T]) TotalBytes() int { + return df.file.TotalBytes() +} + +func (df *StructMmap[T]) Close() error { + return df.file.Close() +} + +type NodeLayout interface { + ID() NodeID +} + +type NodeMmap[T NodeLayout] struct { + *StructMmap[T] +} + +func NewNodeReader[T NodeLayout](file *os.File) (*NodeMmap[T], error) { + sf, err := NewStructReader[T](file) + if err != nil { + return nil, err + } + return &NodeMmap[T]{StructMmap: sf}, nil +} + +func (nf *NodeMmap[T]) FindByID(id NodeID, info *NodeSetInfo) (*T, error) { + // binary search with interpolation + lowOffset := info.StartOffset + targetIdx := id.Index() + lowIdx := info.StartIndex + highOffset := lowOffset + info.Count - 1 + highIdx := info.EndIndex + for lowOffset <= highOffset { + if targetIdx < lowIdx || targetIdx > highIdx { + return nil, fmt.Errorf("node ID %s not present", id.String()) + } + // If nodes are contiguous in this range, compute offset directly + if highIdx-lowIdx == highOffset-lowOffset { + targetOffset := lowOffset + (targetIdx - lowIdx) + return &nf.items[targetOffset], nil + } + // Interpolation search: estimate position based on target's relative position in index range + var mid uint32 + if highIdx > lowIdx { + // Estimate where target should be based on its position in the index range + fraction := float64(targetIdx-lowIdx) / float64(highIdx-lowIdx) + mid = lowOffset + uint32(fraction*float64(highOffset-lowOffset)) + // Ensure mid stays within bounds + if mid < lowOffset { + mid = lowOffset + } else if mid > highOffset { + mid = highOffset + } + } else { + // When indices converge, use simple midpoint + mid = (lowOffset + highOffset) / 2 + } + midNode := &nf.items[mid] + midIdx := (*midNode).ID().Index() + if midIdx == targetIdx { + return midNode, nil + } else if midIdx < targetIdx { + lowOffset = mid + 1 + lowIdx = midIdx + 1 + } else { + highOffset = mid - 1 + highIdx = midIdx - 1 + } + } + return nil, fmt.Errorf("node ID %s not found", id.String()) +} diff --git a/iavl/tree.go b/iavl/tree.go new file mode 100644 index 000000000000..059c93c177a1 --- /dev/null +++ b/iavl/tree.go @@ -0,0 +1,87 @@ +package iavlx + +import corestore "cosmossdk.io/core/store" + +type Tree struct { + origRoot *NodePointer + root *NodePointer + updateBatch *KVUpdateBatch + zeroCopy bool +} + +func NewTree(root *NodePointer, updateBatch *KVUpdateBatch, zeroCopy bool) *Tree { + return &Tree{origRoot: root, root: root, updateBatch: updateBatch, zeroCopy: zeroCopy} +} + +func (tree *Tree) Get(key []byte) ([]byte, error) { + if tree.root == nil { + return nil, nil + } + + root, err := tree.root.Resolve() + if err != nil { + return nil, err + } + + value, _, err := root.Get(key) + if err != nil { + return nil, err + } + + return value, nil +} + +func (tree *Tree) Set(key, value []byte) error { + leafNode := &MemNode{ + height: 0, + size: 1, + version: tree.updateBatch.Version, + key: key, + value: value, + } + ctx := &MutationContext{Version: tree.updateBatch.Version} + newRoot, _, err := setRecursive(tree.root, leafNode, ctx) + if err != nil { + return err + } + + tree.root = newRoot + tree.updateBatch.Updates = append(tree.updateBatch.Updates, KVUpdate{ + SetNode: leafNode, + }) + tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) + return nil +} + +func (tree *Tree) Delete(key []byte) error { + ctx := &MutationContext{Version: tree.updateBatch.Version} + _, newRoot, _, err := removeRecursive(tree.root, key, ctx) + if err != nil { + return err + } + tree.root = newRoot + tree.updateBatch.Updates = append(tree.updateBatch.Updates, KVUpdate{ + DeleteKey: key, + }) + tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) + return nil +} + +func (tree *Tree) Has(key []byte) (bool, error) { + // TODO optimize this + val, err := tree.Get(key) + if err != nil { + return false, err + } + return val != nil, nil +} + +func (tree *Tree) Iterator(start, end []byte) (corestore.Iterator, error) { + return NewIterator(start, end, true, tree.root, tree.zeroCopy), nil +} + +func (tree *Tree) ReverseIterator(start, end []byte) (corestore.Iterator, error) { + return NewIterator(start, end, false, tree.root, tree.zeroCopy), nil +} + +var _ corestore.KVStore = &Tree{} diff --git a/iavl/tree_store.go b/iavl/tree_store.go new file mode 100644 index 000000000000..0519f6aa96ce --- /dev/null +++ b/iavl/tree_store.go @@ -0,0 +1,323 @@ +package iavlx + +import ( + "errors" + "fmt" + "log/slog" + "sync" + "sync/atomic" + + "github.com/tidwall/btree" +) + +type TreeStore struct { + logger *slog.Logger + dir string + + currentWriter *ChangesetWriter + currentChangesetEntry *changesetEntry // Entry for the current batch being written + changesets *btree.Map[uint32, *changesetEntry] + changesetsMapLock sync.RWMutex + savedVersion atomic.Uint32 // Last version with a readable changeset + stagedVersion uint32 // Latest written version (may not be readable yet) + + opts Options + + syncQueue chan *ChangesetFiles + syncDone chan error + + cleanupProc *cleanupProc +} + +type markOrphansReq struct { + version uint32 + orphans [][]NodeID +} + +type changesetEntry struct { + changeset atomic.Pointer[Changeset] +} + +func NewTreeStore(dir string, options Options, logger *slog.Logger) (*TreeStore, error) { + ts := &TreeStore{ + dir: dir, + changesets: &btree.Map[uint32, *changesetEntry]{}, + logger: logger, + opts: options, + } + + err := ts.initNewWriter() + if err != nil { + return nil, fmt.Errorf("failed to initialize first writer: %w", err) + } + + ts.cleanupProc = newCleanupProc(ts) + + if options.WriteWAL && options.WalSyncBuffer >= 0 { + bufferSize := options.GetWalSyncBufferSize() + ts.syncQueue = make(chan *ChangesetFiles, bufferSize) + ts.syncDone = make(chan error) + go ts.syncProc() + } + + return ts, nil +} + +func (ts *TreeStore) initNewWriter() error { + stagedVersion := ts.savedVersion.Load() + 1 + writer, err := NewChangesetWriter(ts.dir, stagedVersion, ts) + if err != nil { + return fmt.Errorf("failed to create changeset writer: %w", err) + } + ts.currentWriter = writer + + return nil +} + +func (ts *TreeStore) getChangesetEntryForVersion(version uint32) *changesetEntry { + ts.changesetsMapLock.RLock() + defer ts.changesetsMapLock.RUnlock() + + var res *changesetEntry + // Find the changeset with the highest start version <= the requested version + ts.changesets.Descend(version, func(key uint32, cs *changesetEntry) bool { + res = cs + return false // Take the first (highest) entry <= version + }) + return res +} + +func (ts *TreeStore) getChangesetForVersion(version uint32) *Changeset { + return ts.getChangesetEntryForVersion(version).changeset.Load() +} + +func (ts *TreeStore) ReadK(nodeId NodeID, _ uint32) (key []byte, err error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + cs.Pin() + defer cs.Unpin() + + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + var offset uint32 + if nodeId.IsLeaf() { + leaf, err := cs.ResolveLeaf(nodeId, 0) + if err != nil { + return nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) + } + offset = leaf.KeyOffset + } else { + branch, err := cs.ResolveBranch(nodeId, 0) + if err != nil { + return nil, fmt.Errorf("failed to resolve branch %s: %w", nodeId.String(), err) + } + offset = branch.KeyOffset + } + + return cs.ReadK(nodeId, offset) +} + +func (ts *TreeStore) ReadKV(nodeId NodeID, _ uint32) (key, value []byte, err error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + cs.Pin() + defer cs.Unpin() + + if cs == nil { + return nil, nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + if !nodeId.IsLeaf() { + return nil, nil, fmt.Errorf("node %s is not a leaf", nodeId.String()) + } + + leaf, err := cs.ResolveLeaf(nodeId, 0) + if err != nil { + return nil, nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) + } + + return cs.ReadKV(nodeId, leaf.KeyOffset) +} + +func (ts *TreeStore) ResolveLeaf(nodeId NodeID) (LeafLayout, error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return LeafLayout{}, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + return cs.ResolveLeaf(nodeId, 0) +} + +func (ts *TreeStore) ResolveBranch(nodeId NodeID) (BranchLayout, error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return BranchLayout{}, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + return cs.ResolveBranch(nodeId, 0) +} + +func (ts *TreeStore) Resolve(nodeId NodeID, _ uint32) (Node, error) { + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + return cs.Resolve(nodeId, 0) +} + +func (ts *TreeStore) SavedVersion() uint32 { + return ts.savedVersion.Load() +} + +func (ts *TreeStore) WriteWALUpdates(updates []KVUpdate) error { + return ts.currentWriter.WriteWALUpdates(updates) +} + +func (ts *TreeStore) WriteWALCommit(version uint32) error { + return ts.currentWriter.WriteWALCommit(version) +} + +func (ts *TreeStore) SaveRoot(version uint32, root *NodePointer, totalLeaves, totalBranches uint32) error { + ts.logger.Debug("saving root", "version", version) + err := ts.currentWriter.SaveRoot(root, version, totalLeaves, totalBranches) + if err != nil { + return err + } + + ts.stagedVersion = version + + currentSize := ts.currentWriter.TotalBytes() + maxSize := ts.opts.GetChangesetMaxTarget() + readerInterval := ts.opts.GetReaderUpdateInterval() + + ts.logger.Debug("saved root", "version", version, "changeset_size", currentSize, "max_size", maxSize, "start_version", ts.currentWriter.StartVersion()) + + // Queue changeset for async WAL sync if enabled + if ts.syncQueue != nil { + select { + case err := <-ts.syncDone: + if err != nil { + return err + } + default: + } + files := ts.currentWriter.files + if files.needsSync.CompareAndSwap(false, true) { + ts.syncQueue <- files + } + } else { + // Otherwise, sync immediately + err := ts.currentWriter.files.kvlogFile.Sync() + if err != nil { + return fmt.Errorf("failed to sync WAL file: %w", err) + } + } + + // Determine if we should create a reader + shouldCreateReader := false + shouldSeal := uint64(currentSize) >= maxSize + + startVersion := ts.currentWriter.StartVersion() + if shouldSeal { + shouldCreateReader = true + } else if readerInterval > 0 { + // Create reader periodically based on interval + versions := version - startVersion + 1 + if versions%readerInterval == 0 { + shouldCreateReader = true + } + } + + if !shouldCreateReader { + // Just continue batching without creating reader + return nil + } + + // Create reader (either shared or sealed) + var reader *Changeset + if shouldSeal { + // Size limit reached - seal the current batch + reader, err = ts.currentWriter.Seal() + if err != nil { + return fmt.Errorf("failed to seal changeset for version %d: %w", version, err) + } + } else { + // Create shared reader for periodic update + reader, err = ts.currentWriter.CreatedSharedReader() + if err != nil { + return fmt.Errorf("failed to create updated changeset reader: %w", err) + } + } + + ts.setActiveReader(startVersion, reader) + ts.savedVersion.Store(version) + + if shouldSeal { + ts.currentChangesetEntry = nil // Reset for next batch + + // Create new writer for next batch + err = ts.initNewWriter() + if err != nil { + return fmt.Errorf("failed to initialize new writer after sealing version %d: %w", version, err) + } + } + + return nil +} + +func (ts *TreeStore) setActiveReader(version uint32, reader *Changeset) { + if ts.currentChangesetEntry == nil { + // First time we're creating an entry for this batch + ts.currentChangesetEntry = &changesetEntry{} + ts.currentChangesetEntry.changeset.Store(reader) + + // Register at the start version only + ts.changesetsMapLock.Lock() + ts.changesets.Set(version, ts.currentChangesetEntry) + ts.changesetsMapLock.Unlock() + } else { + // Update existing entry with new reader + oldReader := ts.currentChangesetEntry.changeset.Swap(reader) + if oldReader != nil { + oldReader.Evict() + if !oldReader.TryDispose() { + ts.cleanupProc.addPendingDisposal(oldReader) + } + } + } +} + +func (ts *TreeStore) MarkOrphans(version uint32, nodeIds [][]NodeID) { + ts.cleanupProc.markOrphans(version, nodeIds) +} + +func (ts *TreeStore) syncProc() { + defer close(ts.syncDone) + for files := range ts.syncQueue { + if err := files.kvlogFile.Sync(); err != nil { + ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err) + return + } + files.needsSync.Store(false) + } +} + +func (ts *TreeStore) Close() error { + ts.cleanupProc.shutdown() + + if ts.syncQueue != nil { + close(ts.syncQueue) + err := <-ts.syncDone + if err != nil { + return err + } + } + + ts.changesetsMapLock.Lock() + + var errs []error + ts.changesets.Scan(func(version uint32, entry *changesetEntry) bool { + errs = append(errs, entry.changeset.Load().Close()) + return true + }) + return errors.Join(errs...) +} diff --git a/iavl/tree_test.go b/iavl/tree_test.go new file mode 100644 index 000000000000..b5e3b72c1914 --- /dev/null +++ b/iavl/tree_test.go @@ -0,0 +1,402 @@ +package iavlx + +import ( + "bytes" + "fmt" + "log/slog" + "os" + "runtime/debug" + "testing" + + corestore "cosmossdk.io/core/store" + sdklog "cosmossdk.io/log" + "github.com/cosmos/iavl" + dbm "github.com/cosmos/iavl/db" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "pgregory.net/rapid" +) + +func TestBasicTest(t *testing.T) { + dir, err := os.MkdirTemp("", "iavlx") + require.NoError(t, err) + defer os.RemoveAll(dir) + commitTree, err := NewCommitTree(dir, Options{}, slog.Default()) + require.NoError(t, err) + tree := commitTree.Branch() + require.NoError(t, tree.Set([]byte{0}, []byte{1})) + // renderTree(t, tree) + + val, err := tree.Get([]byte{0}) + require.NoError(t, err) + require.Equal(t, []byte{1}, val) + + require.NoError(t, tree.Set([]byte{1}, []byte{2})) + //renderTree(t, tree) + + val, err = tree.Get([]byte{0}) + require.NoError(t, err) + require.Equal(t, []byte{1}, val) + val, err = tree.Get([]byte{1}) + require.NoError(t, err) + require.Equal(t, []byte{2}, val) + + require.NoError(t, tree.Set([]byte{2}, []byte{3})) + //renderTree(t, tree) + + val, err = tree.Get([]byte{0}) + require.NoError(t, err) + require.Equal(t, []byte{1}, val) + val, err = tree.Get([]byte{1}) + require.NoError(t, err) + require.Equal(t, []byte{2}, val) + val, err = tree.Get([]byte{2}) + require.NoError(t, err) + require.Equal(t, []byte{3}, val) + + val, err = tree.Get([]byte{3}) + require.NoError(t, err) + require.Nil(t, val) + + require.NoError(t, tree.Delete([]byte{1})) + //renderTree(t, tree) + + val, err = tree.Get([]byte{1}) + require.NoError(t, err) + require.Nil(t, val) + + require.NoError(t, commitTree.Apply(tree)) + hash, err := commitTree.Commit() + require.NoError(t, err) + require.NotNil(t, hash) + t.Logf("committed with root hash: %X", hash) + require.NoError(t, commitTree.Close()) +} + +func renderTree(t interface { + require.TestingT + Logf(format string, args ...any) +}, tree *Tree) { + graph := &bytes.Buffer{} + require.NoError(t, RenderDotGraph(graph, tree.root)) + t.Logf("tree graph:\n%s", graph.String()) +} + +func TestIAVLXSims(t *testing.T) { + rapid.Check(t, testIAVLXSims) +} + +func FuzzIAVLX(f *testing.F) { + f.Fuzz(rapid.MakeFuzz(testIAVLXSims)) +} + +func testIAVLXSims(t *rapid.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("panic recovered: %v\nStack trace:\n%s", r, debug.Stack()) + } + }() + //logger := sdklog.NewTestLogger(t) + logger := sdklog.NewNopLogger() + dbV1 := dbm.NewMemDB() + treeV1 := iavl.NewMutableTree(dbV1, 500000, true, logger) + + tempDir, err := os.MkdirTemp("", "iavlx") + require.NoError(t, err, "failed to create temp directory") + defer os.RemoveAll(tempDir) + treeV2, err := NewCommitTree(tempDir, Options{ + WriteWAL: true, + CompactWAL: true, + DisableCompaction: true, + ZeroCopy: false, + EvictDepth: 0, + WalSyncBuffer: 0, + CompactionOrphanRatio: 0, + CompactionOrphanAge: 0, + RetainVersions: 0, + MinCompactionSeconds: 0, + ChangesetMaxTarget: 1, + CompactAfterVersions: 0, + ReaderUpdateInterval: 1, + }, slog.Default()) + require.NoError(t, err, "failed to create iavlx tree") + simMachine := &SimMachine{ + treeV1: treeV1, + treeV2: treeV2, + existingKeys: map[string][]byte{}, + } + + // TODO switch from StateMachineActions to manually setting up the actions map, this is going to be too magical for other maintainers otherwise + t.Repeat(map[string]func(*rapid.T){ + "": simMachine.Check, + "UpdateN": simMachine.UpdateN, + "GetN": simMachine.GetN, + "Iterate": simMachine.Iterate, + "Commit": simMachine.Commit, + }) + + require.NoError(t, treeV1.Close(), "failed to close iavl tree") + require.NoError(t, treeV2.Close(), "failed to close iavlx tree") +} + +type SimMachine struct { + treeV1 *iavl.MutableTree + treeV2 *CommitTree + // existingKeys keeps track of keys that have been set in the tree or deleted. Deleted keys are retained as nil values. + existingKeys map[string][]byte +} + +func (s *SimMachine) Check(t *rapid.T) { + // after every operation verify the iavlx tree + // after every operation we check that both trees are identical + s.compareIterators(t, nil, nil, true) +} + +func (s *SimMachine) UpdateN(t *rapid.T) { + n := rapid.IntRange(1, 1000).Draw(t, "n") + for i := 0; i < n; i++ { + del := rapid.Bool().Draw(t, "del") + if del { + s.delete(t) + } else { + s.set(t) + } + } +} + +func (s *SimMachine) GetN(t *rapid.T) { + n := rapid.IntRange(1, 1000).Draw(t, "n") + for i := 0; i < n; i++ { + s.get(t) + } +} + +func (s *SimMachine) set(t *rapid.T) { + // choose either a new or an existing key + key := s.selectKey(t) + value := rapid.SliceOfN(rapid.Byte(), 0, 10).Draw(t, "value") + // set in both trees + updated, errV1 := s.treeV1.Set(key, value) + require.NoError(t, errV1, "failed to set key in V1 tree") + branch := s.treeV2.Branch() + require.NoError(t, branch.Set(key, value), "failed to set key in V2 tree") + require.NoError(t, s.treeV2.Apply(branch), "failed to apply batch to V2 tree") + //require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees") + if updated { + require.NotNil(t, s.existingKeys[string(key)], "key shouldn't have been marked as updated") + } else { + existing, found := s.existingKeys[string(key)] + if found { + require.Nil(t, existing, value, "marked as not an update but existin key is nil") + } + } + s.existingKeys[string(key)] = value // mark as existing +} + +func (s *SimMachine) get(t *rapid.T) { + var key = s.selectKey(t) + valueV1, errV1 := s.treeV1.Get(key) + require.NoError(t, errV1, "failed to get key from V1 tree") + valueV2, errV2 := s.treeV2.Branch().Get(key) + require.NoError(t, errV2, "failed to get key from V2 tree") + require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") + expectedValue, found := s.existingKeys[string(key)] + if found { + require.Equal(t, expectedValue, valueV1, "expected value mismatch for key %s", key) + } else { + require.Nil(t, valueV1, "expected nil value for non-existing key %s", key) + } +} + +func (s *SimMachine) selectKey(t *rapid.T) []byte { + if len(s.existingKeys) > 0 && rapid.Bool().Draw(t, "existingKey") { + return []byte(rapid.SampledFrom(maps.Keys(s.existingKeys)).Draw(t, "key")) + } else { + // TODO consider testing longer keys + return rapid.SliceOfN(rapid.Byte(), 0, 10).Draw(t, "key") + } +} + +func (s *SimMachine) delete(t *rapid.T) { + key := s.selectKey(t) + existingValue, found := s.existingKeys[string(key)] + exists := found && existingValue != nil + // delete in both trees + _, removedV1, errV1 := s.treeV1.Remove(key) + require.NoError(t, errV1, "failed to remove key from V1 tree") + branch := s.treeV2.Branch() + require.NoError(t, branch.Delete(key), "failed to remove key from V2 tree") + require.NoError(t, s.treeV2.Apply(branch), "failed to apply batch to V2 tree") + //require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees") + // TODO v1 & v2 have slightly different behaviors for the value returned on removal. We should re-enable this and check. + //if valueV1 == nil || len(valueV1) == 0 { + // require.Empty(t, valueV2, "value should be empty for removed key in V2 tree") + //} else { + // require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") + //} + require.Equal(t, exists, removedV1, "removed status should match existence of key") + s.existingKeys[string(key)] = nil // mark as deleted +} + +func (s *SimMachine) Iterate(t *rapid.T) { + start := s.selectKey(t) + end := s.selectKey(t) + // make sure end is after start + if string(end) <= string(start) { + temp := start + start = end + end = temp + } + + // TODO add cases where we nudge start or end up or down a little + + //ascending := rapid.Bool().Draw(t, "ascending") + + //s.compareIterators(t, start, end, ascending) +} + +func (s *SimMachine) Commit(t *rapid.T) { + hash1, _, err := s.treeV1.SaveVersion() + require.NoError(t, err, "failed to save version in V1 tree") + hash2, err := s.treeV2.Commit() + require.NoError(t, err, "failed to save version in V2 tree") + //s.debugDump(t) + err = VerifyTree(s.treeV2) + //if err != nil { + // branches := s.treeV2.rollingDiff.branchData + // n := branches.Count() + // buf := &bytes.Buffer{} + // for i := uint64(0); i < n; i++ { + // branch, err := branches.Branch(i) + // require.NoError(t, err, "failed to read branch") + // buf.WriteString(fmt.Sprintf("%d: %s\n", i+1, branch)) + // } + // require.NoError(t, os.WriteFile("branches_dump.txt", buf.Bytes(), 0o644)) + // + // buf = &bytes.Buffer{} + // require.NoError(t, s.treeV2.wal.DebugDump(buf)) + // require.NoError(t, os.WriteFile("wal_dump.txt", buf.Bytes(), 0o644)) + //} + require.NoError(t, err, "failed to verify V2 tree") + if !bytes.Equal(hash1, hash2) { + t.Logf("WARNING: hash mismatch between V1 and V2 trees: %X vs %X", hash1, hash2) + } + require.Equal(t, hash1, hash2, "hash mismatch between V1 and V2 trees") + //require.Equal(t, v1, v2, "version mismatch between V1 and V2 trees") +} + +func (s *SimMachine) debugDump(t *rapid.T) { + version := s.treeV1.Version() + t.Logf("Dumping trees at version %d", version) + graph1 := &bytes.Buffer{} + iavl.WriteDOTGraph(graph1, s.treeV1.ImmutableTree, nil) + t.Logf("V1 tree:\n%s", graph1.String()) + //renderTree(t, s.treeV2.Branch()) + iter2, err := s.treeV2.Branch().Iterator(nil, nil) + require.NoError(t, err, "failed to create iterator for V2 tree") + s.debugDumpTree(t, iter2) +} + +func (s *SimMachine) debugDumpTree(t *rapid.T, iter corestore.Iterator) { + dumpStr := "Tree dump:" + defer func() { + require.NoError(t, iter.Close(), "failed to close iterator") + }() + for iter.Valid() { + key := iter.Key() + value := iter.Value() + dumpStr += fmt.Sprintf("\n\tKey: %X, Value: %X", key, value) + iter.Next() + } + t.Log(dumpStr) +} + +//func (s *SimMachine) CheckoutVersion(t *rapid.T) { +// if s.treeV1.Version() <= 1 { +// // cannot checkout version 1 or lower +// return +// } +// s.Commit(t) // make sure we've committed the current version before checking out a previous one +// curVersion := s.treeV1.Version() +// version := rapid.Int64Range(1, curVersion-1).Draw(t, "version") +// itreeV1, err := s.treeV1.GetImmutable(version) +// require.NoError(t, err, "failed to get immutable tree for V1 tree") +// err = s.treeV2.LoadVersion(version) +// require.NoError(t, err, "failed to load version in V2 tree") +// defer require.NoError(t, s.treeV2.LoadVersion(curVersion), "failed to reload current version in V2 tree") +// +// s.debugDumpTree(t) +// +// s.compareIterators(t, nil, nil, true) +// compareIteratorsAtVersion(t, itreeV1, s.treeV2, nil, nil, true) +//} + +func (s *SimMachine) compareIterators(t *rapid.T, start, end []byte, ascending bool) { + iter1, err1 := s.treeV1.Iterator(start, end, ascending) + require.NoError(t, err1, "failed to create iterator for V1 tree") + iter2, err2 := s.treeV2.Branch().Iterator(start, end) + require.NoError(t, err2, "failed to create iterator for V2 tree") + compareIteratorsAtVersion(t, iter1, iter2) +} + +func compareIteratorsAtVersion(t *rapid.T, iterV1 corestore.Iterator, iterV2 corestore.Iterator) { + defer func() { + require.NoError(t, iterV1.Close(), "failed to close iterator for V1 tree") + }() + defer func() { + require.NoError(t, iterV2.Close(), "failed to close iterator for V2 tree") + }() + + for { + hasNextV1 := iterV1.Valid() + hasNextV2 := iterV2.Valid() + require.Equal(t, hasNextV1, hasNextV2, "iterator validity mismatch between V1 and V2 trees") + if !hasNextV1 { + break + } + keyV1 := iterV1.Key() + valueV1 := iterV1.Value() + keyV2 := iterV2.Key() + valueV2 := iterV2.Value() + require.Equal(t, keyV1, keyV2, "key mismatch between V1 and V2 trees") + require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") + iterV1.Next() + iterV2.Next() + } +} + +func TestSimpleOperations(t *testing.T) { + batch := &KVUpdateBatch{Version: 1} + tree := NewTree(nil, batch, false) + require.NoError(t, tree.Set([]byte{1}, []byte{1})) + renderTree(t, tree) + batch.Version = 2 + require.NoError(t, tree.Set([]byte{2}, []byte{2})) + renderTree(t, tree) + batch.Version = 3 + require.NoError(t, tree.Set([]byte{1}, []byte{2})) + renderTree(t, tree) + batch.Version = 4 + require.NoError(t, tree.Set([]byte{3}, []byte{3})) + renderTree(t, tree) +} + +func TestEx1(t *testing.T) { + batch := &KVUpdateBatch{Version: 1} + tree := NewTree(nil, batch, false) + require.NoError(t, tree.Set([]byte{1}, []byte{1})) + require.NoError(t, tree.Set([]byte{2}, []byte{2})) + require.NoError(t, tree.Set([]byte{3}, []byte{3})) + require.NoError(t, tree.Set([]byte{1}, []byte{2})) + require.NoError(t, tree.Set([]byte{3}, []byte{4})) + renderTree(t, tree) +} + +func TestEx2(t *testing.T) { + batch := &KVUpdateBatch{Version: 1} + tree := NewTree(nil, batch, false) + require.NoError(t, tree.Set([]byte{3}, []byte{4})) + require.NoError(t, tree.Set([]byte{2}, []byte{2})) + require.NoError(t, tree.Set([]byte{1}, []byte{2})) + renderTree(t, tree) +} diff --git a/iavl/update.go b/iavl/update.go new file mode 100644 index 000000000000..51342a50dbea --- /dev/null +++ b/iavl/update.go @@ -0,0 +1,37 @@ +package iavlx + +type KVUpdate struct { + SetNode *MemNode + DeleteKey []byte +} + +type KVUpdateBatch struct { + Version uint32 + Orphans [][]NodeID + Updates []KVUpdate +} + +func NewKVUpdateBatch(stagedVersion uint32) *KVUpdateBatch { + return &KVUpdateBatch{ + Version: stagedVersion, + } +} + +type MutationContext struct { + Version uint32 + Orphans []NodeID +} + +func (ctx *MutationContext) MutateBranch(node Node) (*MemNode, error) { + id := node.ID() + if id != 0 { + ctx.Orphans = append(ctx.Orphans, id) + } + return node.MutateBranch(ctx.Version) +} + +func (ctx *MutationContext) AddOrphan(id NodeID) { + if id != 0 { + ctx.Orphans = append(ctx.Orphans, id) + } +} diff --git a/iavl/verify.go b/iavl/verify.go new file mode 100644 index 000000000000..4718e7441ff1 --- /dev/null +++ b/iavl/verify.go @@ -0,0 +1,163 @@ +package iavlx + +import ( + "bytes" + "fmt" +) + +func VerifyTree(tree *CommitTree) error { + latest := tree.latest.Load() + if latest == nil { + return nil + } + + return verifyNode(latest) +} + +type DebugError struct { + Graph string + Err error +} + +func (d *DebugError) Error() string { + return fmt.Sprintf("%v\nDOT graph:\n%s", d.Err, d.Graph) +} + +func (d *DebugError) Unwrap() error { + return d.Err +} + +var _ error = &DebugError{} + +// func verifyNodeDebug(np *NodePointer) error { +// err := verifyNode(np) +// if err != nil { +// var dbgErr *DebugError +// if errors.As(err, &dbgErr) { +// return err +// } else { +// buf := &bytes.Buffer{} +// err2 := RenderDotGraph(buf, np) +// if err2 == nil { +// err = &DebugError{ +// Graph: buf.String(), +// Err: err, +// } +// } +// } +// } +// return err +// } +func verifyNode(np *NodePointer) error { + node, err := np.Resolve() + if err != nil { + return fmt.Errorf("resolve node %s: %w", np.id, err) + } + + if node.Version() != uint32(np.id.Version()) { + return fmt.Errorf("node %s has version %d, expected %d", np.id, node.Version(), np.id.Version()) + } + + if node.IsLeaf() { + if node.Height() != 0 { + return fmt.Errorf("leaf node %s has height %d", np.id, node.Height()) + } + + if node.Size() != 1 { + return fmt.Errorf("leaf node %s has size %d, expected 1", np.id, node.Size()) + } + + if node.Left() != nil { + return fmt.Errorf("leaf node %s has non-nil left child", np.id) + } + + if node.Right() != nil { + return fmt.Errorf("leaf node %s has non-nil right child", np.id) + } + + hash, err := computeHash(node, nil, nil) + if err != nil { + return fmt.Errorf("compute hash for leaf node %s: %w", np.id, err) + } + + if !bytes.Equal(hash, node.Hash()) { + return fmt.Errorf("leaf node %s has invalid hash", np.id) + } + } else { + leftPtr := node.Left() + if leftPtr == nil { + return fmt.Errorf("branch node %s has nil left child", np.id) + } + + rightPtr := node.Right() + if rightPtr == nil { + return fmt.Errorf("branch node %s has nil right child", np.id) + } + + left, err := leftPtr.Resolve() + if err != nil { + return fmt.Errorf("resolve left child of node %s: %w", np.id, err) + } + + right, err := rightPtr.Resolve() + if err != nil { + return fmt.Errorf("resolve right child of node %s: %w", np.id, err) + } + + key, err := node.Key() + if err != nil { + return fmt.Errorf("get key of node %s: %w", np.id, err) + } + + leftKey, err := left.Key() + if err != nil { + return fmt.Errorf("get key of left child of node %s: %w", np.id, err) + } + + rightKey, err := right.Key() + if err != nil { + return fmt.Errorf("get key of right child of node %s: %w", np.id, err) + } + + if bytes.Compare(leftKey, key) >= 0 { + return fmt.Errorf("branch node %s with id %s has key %x, but left child %s, has key %x", node, np.id, key, left, leftKey) + } + + if bytes.Compare(rightKey, key) < 0 { + return fmt.Errorf("branch node %s with id %s has key %x, but right child %s, has key %x", node, np.id, key, right, rightKey) + } + + if left.Size()+right.Size() != node.Size() { + return fmt.Errorf("branch node %s has size %d, but children sizes are %d and %d", np.id, node.Size(), left.Size(), right.Size()) + } + + expectedHeight := maxUint8(left.Height(), right.Height()) + 1 + if node.Height() != expectedHeight { + return fmt.Errorf("branch node %s has height %d, expected %d, left height %d, right height %d", np.id, node.Height(), expectedHeight, left.Height(), right.Height()) + } + + // ensure balanced + balance := int(left.Height()) - int(right.Height()) + if balance < -1 || balance > 1 { + return fmt.Errorf("branch node %s is unbalanced: left height %d, right height %d", np.id, left.Height(), right.Height()) + } + + hash, err := computeHash(node, left.Hash(), right.Hash()) + if err != nil { + return fmt.Errorf("compute hash for branch node %s: %w", np.id, err) + } + + if !bytes.Equal(hash, node.Hash()) { + return fmt.Errorf("branch node %s has invalid hash", np.id) + } + + if err := verifyNode(leftPtr); err != nil { + return err + } + + if err := verifyNode(rightPtr); err != nil { + return err + } + } + return nil +} diff --git a/iavl/version_info.go b/iavl/version_info.go new file mode 100644 index 000000000000..c98ae877d7ca --- /dev/null +++ b/iavl/version_info.go @@ -0,0 +1,27 @@ +package iavlx + +import ( + "fmt" + "unsafe" +) + +func init() { + if unsafe.Sizeof(VersionInfo{}) != VersionInfoSize { + panic(fmt.Sprintf("invalid VersionInfo size: got %d, want %d", unsafe.Sizeof(VersionInfo{}), VersionInfoSize)) + } +} + +const VersionInfoSize = 40 + +type VersionInfo struct { + Leaves NodeSetInfo + Branches NodeSetInfo + RootID NodeID +} + +type NodeSetInfo struct { + StartOffset uint32 + Count uint32 + StartIndex uint32 + EndIndex uint32 +} diff --git a/iavl/writer.go b/iavl/writer.go new file mode 100644 index 000000000000..28e394c3ad6d --- /dev/null +++ b/iavl/writer.go @@ -0,0 +1,62 @@ +package iavlx + +import ( + "bufio" + "fmt" + "io" + "os" + "unsafe" +) + +type FileWriter struct { + writer *bufio.Writer + written int +} + +func NewFileWriter(file *os.File) *FileWriter { + return &FileWriter{ + writer: bufio.NewWriter(file), + } +} + +func (f *FileWriter) Write(p []byte) (n int, err error) { + n, err = f.writer.Write(p) + f.written += n + return n, err +} + +func (f *FileWriter) Flush() error { + if err := f.writer.Flush(); err != nil { + return fmt.Errorf("failed to flush writer: %w", err) + } + return nil +} + +func (f *FileWriter) Size() int { + return f.written +} + +var _ io.Writer = (*FileWriter)(nil) + +type StructWriter[T any] struct { + size int + *FileWriter +} + +func NewStructWriter[T any](file *os.File) *StructWriter[T] { + fw := NewFileWriter(file) + + return &StructWriter[T]{ + size: int(unsafe.Sizeof(*new(T))), + FileWriter: fw, + } +} + +func (sw *StructWriter[T]) Append(x *T) error { + _, err := sw.Write(unsafe.Slice((*byte)(unsafe.Pointer(x)), sw.size)) + return err +} + +func (sw *StructWriter[T]) Count() int { + return sw.written / sw.size +} From a3f1cf2005b9b98ea5bb18d4880d63c44e56c1af Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 20 Oct 2025 13:18:53 -0400 Subject: [PATCH 02/87] feat!: adapt iavlx to store --- iavl/commit_multi_tree.go | 382 ++++++++++++++++++++++++++++++-------- iavl/commit_tree.go | 277 ++++++++++++++++++--------- iavl/go.mod | 45 ++++- iavl/go.sum | 269 +++++++++++++++++++++++++-- iavl/multi_tree.go | 76 +++++++- iavl/tree.go | 106 ++++++++--- iavl/tree_store.go | 6 +- server/util.go | 13 ++ 8 files changed, 943 insertions(+), 231 deletions(-) diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index e9f736b52cde..a8980c157607 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -2,126 +2,344 @@ package iavlx import ( "fmt" - "log/slog" + io "io" "os" - "path/filepath" + "runtime" - storev1beta1 "cosmossdk.io/api/cosmos/store/v1beta1" + "cosmossdk.io/log" + "cosmossdk.io/store/mem" + "cosmossdk.io/store/metrics" + pruningtypes "cosmossdk.io/store/pruning/types" + snapshottypes "cosmossdk.io/store/snapshots/types" + "cosmossdk.io/store/transient" + storetypes "cosmossdk.io/store/types" "github.com/alitto/pond/v2" + dbm "github.com/cosmos/cosmos-db" + protoio "github.com/cosmos/gogoproto/io" ) type CommitMultiTree struct { - trees []*CommitTree - treeNames []string // always ordered by tree name - treesByName map[string]int // index of the trees by name - version uint64 - commitPool pond.ResultPool[[]byte] -} - -func LoadDB(path string, treeNames []string, opts *Options, logger *slog.Logger) (*CommitMultiTree, error) { - n := len(treeNames) - trees := make([]*CommitTree, n) - treesByName := make(map[string]int, n) - for i, name := range treeNames { - if _, exists := treesByName[name]; exists { - return nil, fmt.Errorf("duplicate tree name: %s", name) - } - treesByName[name] = i - dir := filepath.Join(path, name) - err := os.MkdirAll(dir, 0o755) - if err != nil { - return nil, fmt.Errorf("failed to create tree dir %s: %w", dir, err) - } - // Create a logger with tree name context - treeLogger := logger.With("tree", name) - trees[i], err = NewCommitTree(dir, *opts, treeLogger) - if err != nil { - return nil, fmt.Errorf("failed to load tree %s: %w", name, err) + dir string + opts Options + logger log.Logger + trees []storetypes.CommitKVStore // always ordered by tree name + treeKeys []storetypes.StoreKey // always ordered by tree name + storeTypes []storetypes.StoreType // store types by tree index + treesByKey map[storetypes.StoreKey]int // index of the trees by name + + version uint64 + lastCommitId storetypes.CommitID + commitPool pond.ResultPool[storetypes.CommitID] + workingCommitId *storetypes.CommitID +} + +func (db *CommitMultiTree) LastCommitID() storetypes.CommitID { + return db.lastCommitId +} + +func (db *CommitMultiTree) WorkingHash() []byte { + taskGroup := db.commitPool.NewGroup() + stagedVersion := db.version + 1 + for _, tree := range db.trees { + t := tree + taskGroup.Submit(func() storetypes.CommitID { + hash := t.WorkingHash() + return storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hash, + } + }) + } + hashes, err := taskGroup.Wait() + if err != nil { + panic(fmt.Errorf("failed to commit trees: %w", err)) + } + + commitInfo := &storetypes.CommitInfo{} + for i, treeKey := range db.treeKeys { + commitInfo.StoreInfos[i] = storetypes.StoreInfo{ + Name: treeKey.Name(), + CommitId: hashes[i], } } + db.workingCommitId = &storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: commitInfo.Hash(), + } + return db.workingCommitId.Hash +} - db := &CommitMultiTree{ - trees: trees, - treeNames: treeNames, - treesByName: treesByName, - commitPool: pond.NewResultPool[[]byte](n), +func (db *CommitMultiTree) Commit() storetypes.CommitID { + // comput hash (if not done already) + db.WorkingHash() + + // actually commit all trees + taskGroup := db.commitPool.NewGroup() + for _, tree := range db.trees { + t := tree + taskGroup.Submit(func() storetypes.CommitID { + return t.Commit() + }) } - return db, nil + _, err := taskGroup.Wait() + if err != nil { + panic(fmt.Errorf("failed to commit trees: %w", err)) + } + + db.version++ + commitId := db.workingCommitId + db.workingCommitId = nil + db.lastCommitId = *commitId + return *commitId } -func (db *CommitMultiTree) stagedVersion() uint64 { - return db.version + 1 +func (db *CommitMultiTree) SetPruning(options pruningtypes.PruningOptions) { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) GetPruning() pruningtypes.PruningOptions { + return pruningtypes.PruningOptions{} +} + +func (db *CommitMultiTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeMulti } -func (db *CommitMultiTree) LatestVersion() uint64 { - return db.version +func (db *CommitMultiTree) CacheWrap() storetypes.CacheWrap { + return db.CacheMultiStore() } -func (db *CommitMultiTree) Branch() *MultiTree { +func (db *CommitMultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + //TODO implement tracking + return db.CacheMultiStore() +} + +func (db *CommitMultiTree) CacheMultiStore() storetypes.CacheMultiStore { mt := &MultiTree{ - trees: make([]*Tree, len(db.trees)), - treesByName: db.treesByName, // share the map + trees: make([]storetypes.CacheWrap, len(db.trees)), + treesByKey: db.treesByKey, // share the map } for i, root := range db.trees { - mt.trees[i] = root.Branch() + mt.trees[i] = root.CacheWrap() } return mt } -func (db *CommitMultiTree) Apply(mt *MultiTree) error { - if len(mt.trees) != len(db.trees) { - return fmt.Errorf("mismatched number of trees: %d vs %d", len(mt.trees), len(db.trees)) +func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { + return db.trees[db.treesByKey[key]] +} + +func (db *CommitMultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + return db.trees[db.treesByKey[key]] +} + +func (db *CommitMultiTree) TracingEnabled() bool { + return false +} + +func (db *CommitMultiTree) SetTracer(w io.Writer) storetypes.MultiStore { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) Snapshot(height uint64, protoWriter protoio.Writer) error { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) PruneSnapshotHeight(height int64) { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) SetSnapshotInterval(snapshotInterval uint64) { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) Restore(height uint64, format uint32, protoReader protoio.Reader) (snapshottypes.SnapshotItem, error) { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) MountStoreWithDB(key storetypes.StoreKey, typ storetypes.StoreType, _ dbm.DB) { + if _, exists := db.treesByKey[key]; exists { + panic(fmt.Sprintf("store with key %s already mounted", key.Name())) } - for i, tree := range mt.trees { - err := db.trees[i].Apply(tree) + index := len(db.trees) + db.treeKeys = append(db.treeKeys, key) + db.storeTypes = append(db.storeTypes, typ) + db.treesByKey[key] = index +} + +func (db *CommitMultiTree) GetCommitStore(key storetypes.StoreKey) storetypes.CommitStore { + return db.trees[db.treesByKey[key]] +} + +func (db *CommitMultiTree) GetCommitKVStore(key storetypes.StoreKey) storetypes.CommitKVStore { + return db.trees[db.treesByKey[key]] +} + +func (db *CommitMultiTree) LoadLatestVersion() error { + for i, key := range db.treeKeys { + storeType := db.storeTypes[i] + tree, err := db.loadStore(key, storeType) if err != nil { - return fmt.Errorf("failed to apply tree %d: %w", i, err) + return fmt.Errorf("failed to load store %s: %w", key.Name(), err) } + db.trees = append(db.trees, tree) } return nil } -func (db *CommitMultiTree) Commit(logger *slog.Logger) (*storev1beta1.CommitInfo, error) { - taskGroup := db.commitPool.NewGroup() - for _, tree := range db.trees { - t := tree - taskGroup.SubmitErr(func() ([]byte, error) { - if t.root == nil { - logger.Warn("skipping hash of empty tree") - } - return t.Commit() - }) - } - hashes, err := taskGroup.Wait() - if err != nil { - return nil, fmt.Errorf("failed to commit trees: %w", err) - } - db.version++ - commitInfo := &storev1beta1.CommitInfo{ - Version: int64(db.version), - StoreInfos: make([]*storev1beta1.StoreInfo, len(db.trees)), - } - for i, treeName := range db.treeNames { - if hashes[i] == nil { - return nil, fmt.Errorf("tree %s returned nil hash", treeName) +func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.StoreType) (storetypes.CommitKVStore, error) { + switch typ { + case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: + dir := fmt.Sprintf("%s/%s", db.dir, key.Name()) + err := os.MkdirAll(dir, 0o755) + if err != nil { + return nil, fmt.Errorf("failed to create store dir %s: %w", dir, err) + } + return NewCommitTree(dir, db.opts, db.logger.With("store", key.Name())) + case storetypes.StoreTypeTransient: + _, ok := key.(*storetypes.TransientStoreKey) + if !ok { + return nil, fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String()) } - commitInfo.StoreInfos[i] = &storev1beta1.StoreInfo{ - Name: treeName, - CommitId: &storev1beta1.CommitID{ - Version: int64(db.version), - Hash: hashes[i], - }, + + return transient.NewStore(), nil + case storetypes.StoreTypeMemory: + if _, ok := key.(*storetypes.MemoryStoreKey); !ok { + return nil, fmt.Errorf("unexpected key type for a MemoryStoreKey; got: %s", key.String()) } + + return mem.NewStore(), nil + default: + return nil, fmt.Errorf("unsupported store type: %s", typ.String()) + } +} + +func (db *CommitMultiTree) LoadLatestVersionAndUpgrade(upgrades *storetypes.StoreUpgrades) error { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) LoadVersionAndUpgrade(ver int64, upgrades *storetypes.StoreUpgrades) error { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) LoadVersion(ver int64) error { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) SetInterBlockCache(cache storetypes.MultiStorePersistentCache) { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) SetInitialVersion(version int64) error { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) SetIAVLCacheSize(size int) { +} + +func (db *CommitMultiTree) SetIAVLDisableFastNode(disable bool) { +} + +func (db *CommitMultiTree) SetIAVLSyncPruning(sync bool) { +} + +func (db *CommitMultiTree) RollbackToVersion(version int64) error { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) ListeningEnabled(key storetypes.StoreKey) bool { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) AddListeners(keys []storetypes.StoreKey) { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) PopStateCache() []*storetypes.StoreKVPair { + //TODO implement me + panic("implement me") +} + +func (db *CommitMultiTree) SetMetrics(metrics metrics.StoreMetrics) { + //TODO implement me + panic("implement me") +} + +func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, error) { + //n := len(treeNames) + //trees := make([]*CommitTree, n) + //treesByName := make(map[string]int, n) + //for i, name := range treeNames { + // if _, exists := treesByName[name]; exists { + // return nil, fmt.Errorf("duplicate tree name: %s", name) + // } + // treesByName[name] = i + // dir := filepath.Join(path, name) + // err := os.MkdirAll(dir, 0o755) + // if err != nil { + // return nil, fmt.Errorf("failed to create tree dir %s: %w", dir, err) + // } + // // Create a logger with tree name context + // treeLogger := logger.With("tree", name) + // trees[i], err = NewCommitTree(dir, *opts, treeLogger) + // if err != nil { + // return nil, fmt.Errorf("failed to load tree %s: %w", name, err) + // } + //} + // + db := &CommitMultiTree{ + dir: path, + opts: *opts, + commitPool: pond.NewResultPool[storetypes.CommitID](runtime.NumCPU()), + logger: logger, + treesByKey: make(map[storetypes.StoreKey]int), } - return commitInfo, nil + return db, nil +} + +func (db *CommitMultiTree) stagedVersion() uint64 { + return db.version + 1 +} + +func (db *CommitMultiTree) LatestVersion() int64 { + return int64(db.version) } func (db *CommitMultiTree) Close() error { for _, tree := range db.trees { - err := tree.Close() - if err != nil { - return err + if closer, ok := tree.(io.Closer); ok { + err := closer.Close() + if err != nil { + return err + } } + return nil } return nil } + +var _ storetypes.CommitMultiStore = &CommitMultiTree{} diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index d596cb745291..b28fa3aa3a8f 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -2,9 +2,13 @@ package iavlx import ( "fmt" - "log/slog" + "io" "sync" "sync/atomic" + + "cosmossdk.io/log" + pruningtypes "cosmossdk.io/store/pruning/types" + storetypes "cosmossdk.io/store/types" ) type CommitTree struct { @@ -25,10 +29,186 @@ type CommitTree struct { pendingOrphans [][]NodeID - logger *slog.Logger + logger log.Logger + + commitCtx *commitContext + lastCommitId storetypes.CommitID + workingCommitId storetypes.CommitID +} + +func (c *CommitTree) Root() *NodePointer { + return c.root +} + +func (c *CommitTree) ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { + // TODO check channel errors + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if updateBatch.Version != c.stagedVersion() { + return fmt.Errorf("tree version %d does not match staged version %d", updateBatch.Version, c.stagedVersion()) + } + if origRoot != c.root { + // TODO find a way to apply the changes incrementally when roots don't match + return fmt.Errorf("tree original root does not match current root") + } + c.root = newRoot + c.pendingOrphans = append(c.pendingOrphans, updateBatch.Orphans...) + + if c.writeWal { + c.walChan <- updateBatch.Updates + } + + // TODO prevent further writes to the branch tree + + return nil +} + +func (c *CommitTree) Commit() storetypes.CommitID { + commitId, err := c.commit() + if err != nil { + panic(fmt.Sprintf("failed to commit: %v", err)) + } + return commitId +} + +func (c *CommitTree) commit() (storetypes.CommitID, error) { + c.WorkingHash() + commitId := c.workingCommitId + + stagedVersion := c.stagedVersion() + if c.writeWal { + // wait for WAL write to complete + err := <-c.walDone + if err != nil { + return storetypes.CommitID{}, err + } + + err = c.store.WriteWALCommit(stagedVersion) + if err != nil { + return storetypes.CommitID{}, err + } + + c.reinitWalProc() + } + + err := c.store.SaveRoot(stagedVersion, c.root, c.commitCtx.leafNodeIdx, c.commitCtx.branchNodeIdx) + if err != nil { + return storetypes.CommitID{}, err + } + + c.store.MarkOrphans(stagedVersion, c.pendingOrphans) + c.pendingOrphans = nil + + // start eviction if needed + c.startEvict(c.store.SavedVersion()) + + // cache the committed tree as the latest version + c.latest.Store(c.root) + c.version++ + c.lastCommitId = commitId + c.commitCtx = nil + + return commitId, nil +} + +func (c *CommitTree) LastCommitID() storetypes.CommitID { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) WorkingHash() []byte { + if c.commitCtx != nil { + return c.workingCommitId.Hash + } + + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if c.writeWal { + close(c.walChan) + } + + var hash []byte + savedVersion := c.store.SavedVersion() + stagedVersion := c.stagedVersion() + c.commitCtx = &commitContext{ + version: stagedVersion, + savedVersion: savedVersion, + } + if c.root == nil { + hash = emptyHash + } else { + // compute hash and assign node IDs + var err error + hash, err = commitTraverse(c.commitCtx, c.root, 0) + if err != nil { + panic(fmt.Sprintf("failed to compute working hash: %v", err)) + } + } + + c.workingCommitId = storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hash, + } + return hash +} + +func (c *CommitTree) SetPruning(pruningtypes.PruningOptions) { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) GetPruning() pruningtypes.PruningOptions { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) GetStoreType() storetypes.StoreType { + //TODO implement me + panic("implement me") } -func NewCommitTree(dir string, opts Options, logger *slog.Logger) (*CommitTree, error) { +func (c *CommitTree) CacheWrap() storetypes.CacheWrap { + return NewTree(c, c.stagedVersion(), c.zeroCopy) +} + +func (c *CommitTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO support tracing + return c.CacheWrap() +} + +func (c *CommitTree) Get(key []byte) []byte { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) Has(key []byte) bool { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) Set(key, value []byte) { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) Delete(key []byte) { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) Iterator(start, end []byte) storetypes.Iterator { + //TODO implement me + panic("implement me") +} + +func (c *CommitTree) ReverseIterator(start, end []byte) storetypes.Iterator { + //TODO implement me + panic("implement me") +} + +func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, error) { ts, err := NewTreeStore(dir, opts, logger) if err != nil { return nil, fmt.Errorf("failed to create tree store: %w", err) @@ -74,35 +254,6 @@ func (c *CommitTree) reinitWalProc() { }() } -func (c *CommitTree) Branch() *Tree { - return NewTree(c.root, NewKVUpdateBatch(c.stagedVersion()), c.zeroCopy) -} - -func (c *CommitTree) Apply(tree *Tree) error { - // TODO check channel errors - c.writeMutex.Lock() - defer c.writeMutex.Unlock() - - if tree.updateBatch.Version != c.stagedVersion() { - return fmt.Errorf("tree version %d does not match staged version %d", tree.updateBatch.Version, c.stagedVersion()) - } - if tree.origRoot != c.root { - // TODO find a way to apply the changes incrementally when roots don't match - return fmt.Errorf("tree original root does not match current root") - } - c.root = tree.root - batch := tree.updateBatch - c.pendingOrphans = append(c.pendingOrphans, batch.Orphans...) - - if c.writeWal { - c.walChan <- batch.Updates - } - - // TODO prevent further writes to the branch tree - - return nil -} - func (c *CommitTree) startEvict(evictVersion uint32) { if c.evictorRunning { // eviction in progress @@ -130,65 +281,6 @@ func (c *CommitTree) startEvict(evictVersion uint32) { }() } -func (c *CommitTree) Commit() ([]byte, error) { - c.writeMutex.Lock() - defer c.writeMutex.Unlock() - - if c.writeWal { - close(c.walChan) - } - - var hash []byte - savedVersion := c.store.SavedVersion() - stagedVersion := c.stagedVersion() - commitCtx := &commitContext{ - version: stagedVersion, - savedVersion: savedVersion, - } - if c.root == nil { - hash = emptyHash - } else { - // compute hash and assign node IDs - var err error - hash, err = commitTraverse(commitCtx, c.root, 0) - if err != nil { - return nil, err - } - } - - if c.writeWal { - // wait for WAL write to complete - err := <-c.walDone - if err != nil { - return nil, err - } - - err = c.store.WriteWALCommit(stagedVersion) - if err != nil { - return nil, err - } - - c.reinitWalProc() - } - - err := c.store.SaveRoot(stagedVersion, c.root, commitCtx.leafNodeIdx, commitCtx.branchNodeIdx) - if err != nil { - return nil, err - } - - c.store.MarkOrphans(stagedVersion, c.pendingOrphans) - c.pendingOrphans = nil - - // start eviction if needed - c.startEvict(savedVersion) - - // cache the committed tree as the latest version - c.latest.Store(c.root) - c.version++ - - return hash, nil -} - func (c *CommitTree) Close() error { if c.walChan != nil { close(c.walChan) @@ -268,3 +360,6 @@ func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uin count += evictTraverse(memNode.right, depth+1, evictionDepth, evictVersion) return } + +var _ storetypes.CommitKVStore = &CommitTree{} +var _ parentTree = &CommitTree{} diff --git a/iavl/go.mod b/iavl/go.mod index 39c58e1767e3..bec2f1f6655f 100644 --- a/iavl/go.mod +++ b/iavl/go.mod @@ -6,6 +6,7 @@ require ( cosmossdk.io/api v0.9.2 cosmossdk.io/core v1.0.0 cosmossdk.io/log v1.6.1 + cosmossdk.io/store v1.1.2 github.com/alitto/pond/v2 v2.5.0 github.com/cosmos/iavl v1.3.5 github.com/edsrzf/mmap-go v1.2.0 @@ -16,25 +17,59 @@ require ( ) require ( + cosmossdk.io/errors v1.0.2 // indirect + cosmossdk.io/math v1.5.1 // indirect + github.com/DataDog/zstd v1.5.6 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v1.1.2 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cometbft/cometbft v0.38.17 // indirect + github.com/cosmos/cosmos-db v1.1.1 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect github.com/cosmos/gogoproto v1.7.0 // indirect - github.com/cosmos/ics23/go v0.10.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/cosmos/ics23/go v0.11.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/emicklei/dot v1.6.2 // indirect + github.com/getsentry/sentry-go v0.27.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/hashicorp/go-immutable-radix v1.0.0 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/linxGnu/grocksdb v1.8.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/zerolog v1.34.0 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.17.0 // indirect golang.org/x/crypto v0.37.0 // indirect diff --git a/iavl/go.sum b/iavl/go.sum index bba58dacc443..878649ab51db 100644 --- a/iavl/go.sum +++ b/iavl/go.sum @@ -1,75 +1,192 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= cosmossdk.io/core v1.0.0 h1:e7XBbISOytLBOXMVwpRPixThXqEkeLGlg8no/qpgS8U= cosmossdk.io/core v1.0.0/go.mod h1:mKIp3RkoEmtqdEdFHxHwWAULRe+79gfdOvmArrLDbDc= +cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= +cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= +cosmossdk.io/math v1.5.1 h1:c6zo52nBRlqOeSIIQrn/zbxwcNwhaLjTMRn6e4vD7uc= +cosmossdk.io/math v1.5.1/go.mod h1:ToembcWID/wR94cucsMD+2gq6xrlBBOfWcGwC7ZdwZA= +cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o= +cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= +github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= +github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk= +github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNCM= +github.com/cosmos/cosmos-db v1.1.1/go.mod h1:AghjcIPqdhSLP/2Z0yha5xPH3nLnskz81pBx3tcVSAw= github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= github.com/cosmos/iavl v1.3.5 h1:wTDFbaa/L0FVUrwTlzMnjN3fphtKgWxgcZmTc45MZuA= github.com/cosmos/iavl v1.3.5/go.mod h1:T6SfBcyhulVIY2G/ZtAtQm/QiJvsuhIos52V4dWYk88= -github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= -github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= +github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= +github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ= +github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -77,36 +194,104 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae h1:FatpGJD2jmJfhZiFDElaC0QhZUDQnxUeAwTGkfAHN3I= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -123,8 +308,11 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -134,47 +322,82 @@ golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xC golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= @@ -185,18 +408,32 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/iavl/multi_tree.go b/iavl/multi_tree.go index c22f564afae1..f0c21781ee3a 100644 --- a/iavl/multi_tree.go +++ b/iavl/multi_tree.go @@ -1,14 +1,76 @@ package iavlx +import ( + "fmt" + io "io" + + storetypes "cosmossdk.io/store/types" +) + type MultiTree struct { - trees []*Tree // always ordered by tree name - treesByName map[string]int // index of the trees by name + latestVersion int64 + trees []storetypes.CacheWrap // always ordered by tree name + treesByKey map[storetypes.StoreKey]int // index of the trees by name +} + +func (t *MultiTree) Write() { + for _, tree := range t.trees { + tree.Write() + } +} + +func (t *MultiTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeMulti } -func (t *MultiTree) TreeByName(name string) *Tree { - idx, ok := t.treesByName[name] - if !ok { - return nil +func (t *MultiTree) CacheWrap() storetypes.CacheWrap { + return t.CacheMultiStore() +} + +func (t *MultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + //TODO implement tracing + return t.CacheWrap() +} + +func (t *MultiTree) CacheMultiStore() storetypes.CacheMultiStore { + wrapped := &MultiTree{ + trees: make([]storetypes.CacheKVStore, len(t.trees)), + treesByKey: t.treesByKey, } - return t.trees[idx] + for i, tree := range t.trees { + wrapped.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) + } + return wrapped +} + +func (t *MultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { + return nil, fmt.Errorf("CacheMultiStoreWithVersion can only be called on CommitMultiStore") } + +func (t *MultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { + return t.trees[t.treesByKey[key]] +} + +func (t *MultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + return t.trees[t.treesByKey[key]] +} + +func (t *MultiTree) TracingEnabled() bool { + return false +} + +func (t *MultiTree) SetTracer(w io.Writer) storetypes.MultiStore { + //TODO implement me + panic("implement me") +} + +func (t *MultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { + //TODO implement me + panic("implement me") +} + +func (t *MultiTree) LatestVersion() int64 { + return t.latestVersion +} + +var _ storetypes.CacheMultiStore = &MultiTree{} diff --git a/iavl/tree.go b/iavl/tree.go index 059c93c177a1..53e50aad8a9d 100644 --- a/iavl/tree.go +++ b/iavl/tree.go @@ -1,37 +1,93 @@ package iavlx -import corestore "cosmossdk.io/core/store" +import ( + io "io" + + corestore "cosmossdk.io/core/store" + storetypes "cosmossdk.io/store/types" +) type Tree struct { + parent parentTree origRoot *NodePointer root *NodePointer - updateBatch *KVUpdateBatch + updateBatch KVUpdateBatch zeroCopy bool } -func NewTree(root *NodePointer, updateBatch *KVUpdateBatch, zeroCopy bool) *Tree { - return &Tree{origRoot: root, root: root, updateBatch: updateBatch, zeroCopy: zeroCopy} +type parentTree interface { + Root() *NodePointer + ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error +} + +func NewTree(parent parentTree, stagedVersion uint32, zeroCopy bool) *Tree { + root := parent.Root() + return &Tree{ + parent: parent, + root: root, + origRoot: root, + updateBatch: KVUpdateBatch{Version: stagedVersion}, + zeroCopy: zeroCopy, + } +} + +func (tree *Tree) Root() *NodePointer { + return tree.root +} + +func (tree *Tree) ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { + if tree.root != origRoot { + panic("cannot apply changes: root has changed") + } + tree.root = newRoot + tree.updateBatch.Updates = append(tree.updateBatch.Updates, updateBatch.Updates...) + tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, updateBatch.Orphans...) + return nil +} + +func (tree *Tree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeIAVL } -func (tree *Tree) Get(key []byte) ([]byte, error) { +func (tree *Tree) CacheWrap() storetypes.CacheWrap { + //TODO implement me + panic("implement me") +} + +func (tree *Tree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO support tracing + return tree.CacheWrap() +} + +func (tree *Tree) Write() { + err := tree.parent.ApplyChanges(tree.origRoot, tree.root, tree.updateBatch) + if err != nil { + panic(err) + } + tree.updateBatch.Updates = nil + tree.updateBatch.Orphans = nil + tree.root = tree.parent.Root() +} + +func (tree *Tree) Get(key []byte) []byte { if tree.root == nil { - return nil, nil + return nil } root, err := tree.root.Resolve() if err != nil { - return nil, err + panic(err) } value, _, err := root.Get(key) if err != nil { - return nil, err + panic(err) } - return value, nil + return value } -func (tree *Tree) Set(key, value []byte) error { +func (tree *Tree) Set(key, value []byte) { leafNode := &MemNode{ height: 0, size: 1, @@ -42,7 +98,7 @@ func (tree *Tree) Set(key, value []byte) error { ctx := &MutationContext{Version: tree.updateBatch.Version} newRoot, _, err := setRecursive(tree.root, leafNode, ctx) if err != nil { - return err + panic(err) } tree.root = newRoot @@ -50,38 +106,34 @@ func (tree *Tree) Set(key, value []byte) error { SetNode: leafNode, }) tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) - return nil } -func (tree *Tree) Delete(key []byte) error { +func (tree *Tree) Delete(key []byte) { ctx := &MutationContext{Version: tree.updateBatch.Version} _, newRoot, _, err := removeRecursive(tree.root, key, ctx) if err != nil { - return err + panic(err) } tree.root = newRoot tree.updateBatch.Updates = append(tree.updateBatch.Updates, KVUpdate{ DeleteKey: key, }) tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) - return nil } -func (tree *Tree) Has(key []byte) (bool, error) { - // TODO optimize this - val, err := tree.Get(key) - if err != nil { - return false, err - } - return val != nil, nil +func (tree *Tree) Has(key []byte) bool { + // TODO optimize this if possible + val := tree.Get(key) + return val != nil } -func (tree *Tree) Iterator(start, end []byte) (corestore.Iterator, error) { - return NewIterator(start, end, true, tree.root, tree.zeroCopy), nil +func (tree *Tree) Iterator(start, end []byte) corestore.Iterator { + return NewIterator(start, end, true, tree.root, tree.zeroCopy) } -func (tree *Tree) ReverseIterator(start, end []byte) (corestore.Iterator, error) { - return NewIterator(start, end, false, tree.root, tree.zeroCopy), nil +func (tree *Tree) ReverseIterator(start, end []byte) corestore.Iterator { + return NewIterator(start, end, false, tree.root, tree.zeroCopy) } -var _ corestore.KVStore = &Tree{} +var _ storetypes.CacheKVStore = &Tree{} +var _ parentTree = &Tree{} diff --git a/iavl/tree_store.go b/iavl/tree_store.go index 0519f6aa96ce..032a3e60abdd 100644 --- a/iavl/tree_store.go +++ b/iavl/tree_store.go @@ -3,15 +3,15 @@ package iavlx import ( "errors" "fmt" - "log/slog" "sync" "sync/atomic" + "cosmossdk.io/log" "github.com/tidwall/btree" ) type TreeStore struct { - logger *slog.Logger + logger log.Logger dir string currentWriter *ChangesetWriter @@ -38,7 +38,7 @@ type changesetEntry struct { changeset atomic.Pointer[Changeset] } -func NewTreeStore(dir string, options Options, logger *slog.Logger) (*TreeStore, error) { +func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, error) { ts := &TreeStore{ dir: dir, changesets: &btree.Map[uint32, *changesetEntry]{}, diff --git a/server/util.go b/server/util.go index 42d01bd65d09..37b13f6fb43f 100644 --- a/server/util.go +++ b/server/util.go @@ -25,10 +25,12 @@ import ( "golang.org/x/sync/errgroup" "cosmossdk.io/log" + "cosmossdk.io/store" "cosmossdk.io/store/snapshots" snapshottypes "cosmossdk.io/store/snapshots/types" storetypes "cosmossdk.io/store/types" + iavlx "github.com/cosmos/cosmos-sdk/iavl" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client/flags" @@ -582,6 +584,17 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) { defaultMempool, baseapp.SetChainID(chainID), baseapp.SetQueryGasLimit(cast.ToUint64(appOpts.Get(FlagQueryGasLimit))), + func(bapp *baseapp.BaseApp) { + db, err := iavlx.LoadDB( + filepath.Join(homeDir, "data", "iavlx"), + &iavlx.Options{}, + bapp.Logger(), + ) + if err != nil { + panic(fmt.Errorf("failed to load iavlx db: %w", err)) + } + bapp.SetCMS(db) + }, } } From 1041204ee779ff898ec98326e31bb68f4bb45118 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 20 Oct 2025 13:20:33 -0400 Subject: [PATCH 03/87] fixes --- iavl/multi_tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iavl/multi_tree.go b/iavl/multi_tree.go index f0c21781ee3a..451dd9c880f6 100644 --- a/iavl/multi_tree.go +++ b/iavl/multi_tree.go @@ -9,7 +9,7 @@ import ( type MultiTree struct { latestVersion int64 - trees []storetypes.CacheWrap // always ordered by tree name + trees []storetypes.CacheKVStore // always ordered by tree name treesByKey map[storetypes.StoreKey]int // index of the trees by name } @@ -34,8 +34,8 @@ func (t *MultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) func (t *MultiTree) CacheMultiStore() storetypes.CacheMultiStore { wrapped := &MultiTree{ - trees: make([]storetypes.CacheKVStore, len(t.trees)), treesByKey: t.treesByKey, + trees: make([]storetypes.CacheKVStore, len(t.trees)), } for i, tree := range t.trees { wrapped.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) From 2cbdf7b18a5768ec5a16d6fb6b5f9dafa61cfa4d Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 20 Oct 2025 13:25:27 -0400 Subject: [PATCH 04/87] build fixes --- go.mod | 8 +- go.sum | 3 + iavl/commit_multi_tree.go | 11 +- iavl/compactor.go | 7 +- iavl/go.mod | 83 ------- iavl/go.sum | 439 -------------------------------------- simapp/go.mod | 2 + simapp/go.sum | 3 + tests/go.mod | 2 + tests/go.sum | 3 + tests/systemtests/go.mod | 2 + tests/systemtests/go.sum | 3 + 12 files changed, 33 insertions(+), 533 deletions(-) delete mode 100644 iavl/go.mod delete mode 100644 iavl/go.sum diff --git a/go.mod b/go.mod index cf788aa6ef99..109beed1e644 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( cosmossdk.io/store v1.1.2 cosmossdk.io/x/tx v0.14.0 github.com/99designs/keyring v1.2.1 + github.com/alitto/pond/v2 v2.5.0 github.com/bgentry/speakeasy v0.2.0 github.com/bits-and-blooms/bitset v1.24.1 github.com/chzyer/readline v1.5.1 @@ -25,8 +26,10 @@ require ( github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogogateway v1.2.0 github.com/cosmos/gogoproto v1.7.0 + github.com/cosmos/iavl v1.2.6 github.com/cosmos/ledger-cosmos-go v0.16.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 + github.com/edsrzf/mmap-go v1.0.0 github.com/golang/protobuf v1.5.4 github.com/google/go-cmp v0.7.0 github.com/google/gofuzz v1.2.0 @@ -55,8 +58,10 @@ require ( github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/tendermint/go-amino v0.16.0 + github.com/tidwall/btree v1.8.1 go.uber.org/mock v0.6.0 golang.org/x/crypto v0.43.0 + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 golang.org/x/sync v0.17.0 google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c google.golang.org/grpc v1.76.0 @@ -116,7 +121,6 @@ require ( github.com/cockroachdb/redact v1.1.6 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect github.com/cometbft/cometbft-db v0.14.1 // indirect - github.com/cosmos/iavl v1.2.6 // indirect github.com/cosmos/ics23/go v0.11.0 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -194,7 +198,6 @@ require ( github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect - github.com/tidwall/btree v1.8.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect github.com/zeebo/errs v1.4.0 // indirect @@ -216,7 +219,6 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.21.0 // indirect - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.45.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/sys v0.37.0 // indirect diff --git a/go.sum b/go.sum index 6c66e6512eab..b6cf458360cd 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= +github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -270,6 +272,7 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index a8980c157607..a7341209a694 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -7,15 +7,16 @@ import ( "runtime" "cosmossdk.io/log" + "github.com/alitto/pond/v2" + dbm "github.com/cosmos/cosmos-db" + protoio "github.com/cosmos/gogoproto/io" + "cosmossdk.io/store/mem" "cosmossdk.io/store/metrics" pruningtypes "cosmossdk.io/store/pruning/types" snapshottypes "cosmossdk.io/store/snapshots/types" "cosmossdk.io/store/transient" storetypes "cosmossdk.io/store/types" - "github.com/alitto/pond/v2" - dbm "github.com/cosmos/cosmos-db" - protoio "github.com/cosmos/gogoproto/io" ) type CommitMultiTree struct { @@ -117,11 +118,11 @@ func (db *CommitMultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceCo func (db *CommitMultiTree) CacheMultiStore() storetypes.CacheMultiStore { mt := &MultiTree{ - trees: make([]storetypes.CacheWrap, len(db.trees)), + trees: make([]storetypes.CacheKVStore, len(db.trees)), treesByKey: db.treesByKey, // share the map } for i, root := range db.trees { - mt.trees[i] = root.CacheWrap() + mt.trees[i] = root.CacheWrap().(storetypes.CacheKVStore) } return mt } diff --git a/iavl/compactor.go b/iavl/compactor.go index dbba338a972c..e344942fadf7 100644 --- a/iavl/compactor.go +++ b/iavl/compactor.go @@ -3,7 +3,8 @@ package iavlx import ( "errors" "fmt" - "log/slog" + + "cosmossdk.io/log" ) type CompactOptions struct { @@ -15,7 +16,7 @@ type CompactOptions struct { type RetainCriteria func(createVersion, orphanVersion uint32) bool type Compactor struct { - logger *slog.Logger + logger log.Logger criteria RetainCriteria compactWAL bool @@ -40,7 +41,7 @@ type Compactor struct { branchOrphanVersionTotal uint64 } -func NewCompacter(logger *slog.Logger, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { +func NewCompacter(logger log.Logger, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { if reader.files == nil { return nil, fmt.Errorf("changeset has no associated files, cannot compact a shared changeset reader which files set to nil") } diff --git a/iavl/go.mod b/iavl/go.mod deleted file mode 100644 index bec2f1f6655f..000000000000 --- a/iavl/go.mod +++ /dev/null @@ -1,83 +0,0 @@ -module github.com/cosmos/cosmos-sdk/iavl - -go 1.24.0 - -require ( - cosmossdk.io/api v0.9.2 - cosmossdk.io/core v1.0.0 - cosmossdk.io/log v1.6.1 - cosmossdk.io/store v1.1.2 - github.com/alitto/pond/v2 v2.5.0 - github.com/cosmos/iavl v1.3.5 - github.com/edsrzf/mmap-go v1.2.0 - github.com/stretchr/testify v1.11.1 - github.com/tidwall/btree v1.8.1 - golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b - pgregory.net/rapid v1.2.0 -) - -require ( - cosmossdk.io/errors v1.0.2 // indirect - cosmossdk.io/math v1.5.1 // indirect - github.com/DataDog/zstd v1.5.6 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bytedance/sonic v1.14.0 // indirect - github.com/bytedance/sonic/loader v0.3.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect - github.com/cockroachdb/apd/v3 v3.2.1 // indirect - github.com/cockroachdb/errors v1.11.3 // indirect - github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v1.1.2 // indirect - github.com/cockroachdb/redact v1.1.5 // indirect - github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/cometbft/cometbft v0.38.17 // indirect - github.com/cosmos/cosmos-db v1.1.1 // indirect - github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect - github.com/cosmos/gogoproto v1.7.0 // indirect - github.com/cosmos/ics23/go v0.11.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/emicklei/dot v1.6.2 // indirect - github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/hashicorp/go-immutable-radix v1.0.0 // indirect - github.com/hashicorp/go-metrics v0.5.4 // indirect - github.com/hashicorp/golang-lru v1.0.2 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/linxGnu/grocksdb v1.8.14 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae // indirect - github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.5 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/rs/zerolog v1.34.0 // indirect - github.com/sasha-s/go-deadlock v0.3.5 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.17.0 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.24.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect - google.golang.org/grpc v1.72.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/iavl/go.sum b/iavl/go.sum deleted file mode 100644 index 878649ab51db..000000000000 --- a/iavl/go.sum +++ /dev/null @@ -1,439 +0,0 @@ -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= -cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= -cosmossdk.io/core v1.0.0 h1:e7XBbISOytLBOXMVwpRPixThXqEkeLGlg8no/qpgS8U= -cosmossdk.io/core v1.0.0/go.mod h1:mKIp3RkoEmtqdEdFHxHwWAULRe+79gfdOvmArrLDbDc= -cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= -cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= -cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= -cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= -cosmossdk.io/math v1.5.1 h1:c6zo52nBRlqOeSIIQrn/zbxwcNwhaLjTMRn6e4vD7uc= -cosmossdk.io/math v1.5.1/go.mod h1:ToembcWID/wR94cucsMD+2gq6xrlBBOfWcGwC7ZdwZA= -cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o= -cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= -github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= -github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= -github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= -github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= -github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= -github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= -github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= -github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= -github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= -github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= -github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk= -github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNCM= -github.com/cosmos/cosmos-db v1.1.1/go.mod h1:AghjcIPqdhSLP/2Z0yha5xPH3nLnskz81pBx3tcVSAw= -github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= -github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= -github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= -github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= -github.com/cosmos/iavl v1.3.5 h1:wTDFbaa/L0FVUrwTlzMnjN3fphtKgWxgcZmTc45MZuA= -github.com/cosmos/iavl v1.3.5/go.mod h1:T6SfBcyhulVIY2G/ZtAtQm/QiJvsuhIos52V4dWYk88= -github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= -github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= -github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= -github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= -github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= -github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= -github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ= -github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae h1:FatpGJD2jmJfhZiFDElaC0QhZUDQnxUeAwTGkfAHN3I= -github.com/oasisprotocol/curve25519-voi v0.0.0-20220708102147-0a8a51822cae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= -github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= -github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= -github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= -github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= -github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA= -github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= -golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA= -golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= -pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/simapp/go.mod b/simapp/go.mod index a44232878112..72b8632b96dd 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -46,6 +46,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + github.com/alitto/pond/v2 v2.5.0 // indirect github.com/aws/aws-sdk-go-v2 v1.39.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.31.8 // indirect @@ -100,6 +101,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index b4b150cbe756..7b66c6d8919c 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -84,6 +84,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= +github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -283,6 +285,7 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/tests/go.mod b/tests/go.mod index a5e75a43ea1f..aab2572a11f7 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -49,6 +49,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + github.com/alitto/pond/v2 v2.5.0 // indirect github.com/aws/aws-sdk-go-v2 v1.39.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.31.8 // indirect @@ -99,6 +100,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect diff --git a/tests/go.sum b/tests/go.sum index b71d72a5075b..8b13326a0cd3 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -82,6 +82,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= +github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -275,6 +277,7 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index d15a8e0e4dfd..2b33763dcc46 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -32,6 +32,7 @@ require ( github.com/99designs/keyring v1.2.2 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect + github.com/alitto/pond/v2 v2.5.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect @@ -65,6 +66,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index c077e2a5a749..68219c93c868 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -52,6 +52,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= +github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -197,6 +199,7 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= From dc876a42354ce3bcc3dca85581830516fce089a8 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 20 Oct 2025 13:33:55 -0400 Subject: [PATCH 05/87] WIP on system tests --- Makefile | 79 ++++++++++++++++++++++++--------------- iavl/commit_multi_tree.go | 17 ++++++--- iavl/commit_tree.go | 1 + 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 0e935ce2b185..d5be191b11b4 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ifeq ($(findstring -,$(VERSION)),) # No "-" means it's just a hash VERSION := 0.0.0-$(VERSION_RAW) endif export VERSION -export CMTVERSION := $(shell go list -m github.com/cometbft/cometbft | sed 's:.* ::') +export CMTVERSION := $(shell go list -m github.com/cometbft/cometbft/v2 | sed 's:.* ::') export COMMIT := $(shell git log -1 --format='%H') LEDGER_ENABLED ?= true BINDIR ?= $(GOPATH)/bin @@ -67,7 +67,7 @@ ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=sim \ -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" \ - -X github.com/cometbft/cometbft/version.TMCoreSemVer=$(CMTVERSION) + -X github.com/cometbft/cometbft/v2/version.TMCoreSemVer=$(CMTVERSION) # DB backend selection ifeq (cleveldb,$(findstring cleveldb,$(COSMOS_BUILD_OPTIONS))) @@ -134,11 +134,15 @@ cosmovisor: confix: $(MAKE) -C tools/confix confix +hubl: + $(MAKE) -C tools/hubl hubl + .PHONY: build build-linux-amd64 build-linux-arm64 cosmovisor confix + #? mocks: Generate mock file mocks: $(MOCKS_DIR) - @go install go.uber.org/mock/mockgen@v0.6.0 + @go install go.uber.org/mock/mockgen@v0.5.0 sh ./scripts/mockgen.sh .PHONY: mocks @@ -379,7 +383,7 @@ benchmark: ### Linting ### ############################################################################### -golangci_version=v2.5.0 +golangci_version=v2.3.1 lint-install: @echo "--> Installing golangci-lint $(golangci_version)" @@ -387,12 +391,13 @@ lint-install: lint: @echo "--> Running linter on all files" - @$(MAKE) lint-install + $(MAKE) lint-install @./scripts/go-lint-all.bash --timeout=15m + lint-fix: @echo "--> Running linter" - @$(MAKE) lint-install + $(MAKE) lint-install @./scripts/go-lint-all.bash --fix .PHONY: lint lint-fix @@ -426,44 +431,52 @@ proto-lint: proto-check-breaking: @$(protoImage) buf breaking --against $(HTTPS_GIT)#branch=main -CMT_URL = https://raw.githubusercontent.com/cometbft/cometbft/v0.38.0/proto/tendermint +CMT_VERSION_DIR = v1.0.1 +CMT_PROTO = v1 +CMT_URL = https://raw.githubusercontent.com/cometbft/cometbft/$(CMT_VERSION_DIR)/proto/cometbft +CMT_CRYPTO_TYPES = proto/cometbft/crypto/$(CMT_PROTO) +CMT_ABCI_TYPES = proto/cometbft/abci/$(CMT_PROTO) +CMT_TYPES = proto/cometbft/types/$(CMT_PROTO) +CMT_VERSION = proto/cometbft/version/$(CMT_PROTO) +CMT_LIBS = proto/cometbft/libs/bits/$(CMT_PROTO) +CMT_P2P = proto/cometbft/p2p/$(CMT_PROTO) -CMT_CRYPTO_TYPES = proto/tendermint/crypto -CMT_ABCI_TYPES = proto/tendermint/abci -CMT_TYPES = proto/tendermint/types -CMT_VERSION = proto/tendermint/version -CMT_LIBS = proto/tendermint/libs/bits -CMT_P2P = proto/tendermint/p2p - -proto-update-deps: - @echo "Updating Protobuf dependencies" +proto-update-comet: + @echo "Updating Protobuf dependency: downloading cometbft.$(CMT_PROTO) files from CometBFT $(CMT_VERSION_DIR)" @mkdir -p $(CMT_ABCI_TYPES) - @curl -sSL $(CMT_URL)/abci/types.proto > $(CMT_ABCI_TYPES)/types.proto + @curl -fsSL $(CMT_URL)/abci/$(CMT_PROTO)/service.proto > $(CMT_ABCI_TYPES)/service.proto + @curl -fsSL $(CMT_URL)/abci/$(CMT_PROTO)/types.proto > $(CMT_ABCI_TYPES)/types.proto @mkdir -p $(CMT_VERSION) - @curl -sSL $(CMT_URL)/version/types.proto > $(CMT_VERSION)/types.proto + @curl -fsSL $(CMT_URL)/version/$(CMT_PROTO)/types.proto > $(CMT_VERSION)/types.proto @mkdir -p $(CMT_TYPES) - @curl -sSL $(CMT_URL)/types/types.proto > $(CMT_TYPES)/types.proto - @curl -sSL $(CMT_URL)/types/evidence.proto > $(CMT_TYPES)/evidence.proto - @curl -sSL $(CMT_URL)/types/params.proto > $(CMT_TYPES)/params.proto - @curl -sSL $(CMT_URL)/types/validator.proto > $(CMT_TYPES)/validator.proto - @curl -sSL $(CMT_URL)/types/block.proto > $(CMT_TYPES)/block.proto + @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/block.proto > $(CMT_TYPES)/block.proto + @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/canonical.proto > $(CMT_TYPES)/canonical.proto + @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/events.proto > $(CMT_TYPES)/events.proto + @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/evidence.proto > $(CMT_TYPES)/evidence.proto + @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/params.proto > $(CMT_TYPES)/params.proto + @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/types.proto > $(CMT_TYPES)/types.proto + @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/validator.proto > $(CMT_TYPES)/validator.proto @mkdir -p $(CMT_CRYPTO_TYPES) - @curl -sSL $(CMT_URL)/crypto/proof.proto > $(CMT_CRYPTO_TYPES)/proof.proto - @curl -sSL $(CMT_URL)/crypto/keys.proto > $(CMT_CRYPTO_TYPES)/keys.proto + @curl -fsSL $(CMT_URL)/crypto/$(CMT_PROTO)/keys.proto > $(CMT_CRYPTO_TYPES)/keys.proto + @curl -fsSL $(CMT_URL)/crypto/$(CMT_PROTO)/proof.proto > $(CMT_CRYPTO_TYPES)/proof.proto @mkdir -p $(CMT_LIBS) - @curl -sSL $(CMT_URL)/libs/bits/types.proto > $(CMT_LIBS)/types.proto + @curl -fsSL $(CMT_URL)/libs/bits/$(CMT_PROTO)/types.proto > $(CMT_LIBS)/types.proto @mkdir -p $(CMT_P2P) - @curl -sSL $(CMT_URL)/p2p/types.proto > $(CMT_P2P)/types.proto + @curl -fsSL $(CMT_URL)/p2p/$(CMT_PROTO)/conn.proto > $(CMT_P2P)/conn.proto + @curl -fsSL $(CMT_URL)/p2p/$(CMT_PROTO)/pex.proto > $(CMT_P2P)/pex.proto + @curl -fsSL $(CMT_URL)/p2p/$(CMT_PROTO)/types.proto > $(CMT_P2P)/types.proto - $(DOCKER) run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName) buf mod update +proto-update-deps: + @echo "Updating Protobuf dependencies: running 'buf dep update'" + $(DOCKER) run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName) buf dep update -.PHONY: proto-all proto-gen proto-swagger-gen proto-format proto-lint proto-check-breaking proto-update-deps +.PHONY: proto-all proto-gen proto-swagger-gen proto-format proto-lint proto-check-breaking proto-update-deps proto-update-comet ############################################################################### ### Localnet ### @@ -492,13 +505,17 @@ localnet-debug: localnet-stop localnet-build-dlv localnet-build-nodes .PHONY: localnet-start localnet-stop localnet-debug localnet-build-env localnet-build-dlv localnet-build-nodes -test-system: build-v53 build +build-system-test-current: build + mkdir -p ./tests/systemtests/binaries/ + cp $(BUILDDIR)/simd ./tests/systemtests/binaries/ + +test-system: build-v53 build-system-test-current mkdir -p ./tests/systemtests/binaries/ cp $(BUILDDIR)/simd ./tests/systemtests/binaries/ mkdir -p ./tests/systemtests/binaries/v0.53 mv $(BUILDDIR)/simdv53 ./tests/systemtests/binaries/v0.53/simd $(MAKE) -C tests/systemtests test -.PHONY: test-system +.PHONY: test-system build-system-test-current # build-v53 checks out the v0.53.x branch, builds the binary, and renames it to simdv53. build-v53: diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index a7341209a694..d756319ed019 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -121,15 +121,21 @@ func (db *CommitMultiTree) CacheMultiStore() storetypes.CacheMultiStore { trees: make([]storetypes.CacheKVStore, len(db.trees)), treesByKey: db.treesByKey, // share the map } - for i, root := range db.trees { - mt.trees[i] = root.CacheWrap().(storetypes.CacheKVStore) + for i, tree := range db.trees { + mt.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) } return mt } func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { - //TODO implement me - panic("implement me") + if version == 0 { + version = int64(db.version) + } + if uint64(version) == db.version { + // TODO we actually want to cache wrap the latest saved version, not the working version + return db.CacheMultiStore(), nil + } + return nil, fmt.Errorf("checking out historical versions has not been implemented yet") } func (db *CommitMultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { @@ -247,8 +253,7 @@ func (db *CommitMultiTree) LoadVersion(ver int64) error { } func (db *CommitMultiTree) SetInterBlockCache(cache storetypes.MultiStorePersistentCache) { - //TODO implement me - panic("implement me") + db.logger.Warn("SetInterBlockCache is not implemented for CommitMultiTree") } func (db *CommitMultiTree) SetInitialVersion(version int64) error { diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index b28fa3aa3a8f..1d6005c9a689 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "cosmossdk.io/log" + pruningtypes "cosmossdk.io/store/pruning/types" storetypes "cosmossdk.io/store/types" ) From c3d966524645a0c6906bdeda13e9c5e7b9cfb8e9 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 20 Oct 2025 19:08:22 -0400 Subject: [PATCH 06/87] WIP on system tests --- iavl/commit_multi_tree.go | 50 +++++++----------- iavl/commit_tree.go | 50 ++++++++++-------- iavl/tree.go | 4 +- iavl/tree_test.go | 103 +++++++++++--------------------------- 4 files changed, 79 insertions(+), 128 deletions(-) diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index d756319ed019..6177c9ed508b 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -95,12 +95,11 @@ func (db *CommitMultiTree) Commit() storetypes.CommitID { } func (db *CommitMultiTree) SetPruning(options pruningtypes.PruningOptions) { - //TODO implement me - panic("implement me") + db.logger.Warn("SetPruning is not implemented for CommitMultiTree") } func (db *CommitMultiTree) GetPruning() pruningtypes.PruningOptions { - return pruningtypes.PruningOptions{} + return pruningtypes.NewPruningOptions(pruningtypes.PruningDefault) } func (db *CommitMultiTree) GetStoreType() storetypes.StoreType { @@ -151,33 +150,29 @@ func (db *CommitMultiTree) TracingEnabled() bool { } func (db *CommitMultiTree) SetTracer(w io.Writer) storetypes.MultiStore { - //TODO implement me - panic("implement me") + db.logger.Warn("SetTracer is not implemented for CommitMultiTree") + return db } func (db *CommitMultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { - //TODO implement me - panic("implement me") + db.logger.Warn("SetTracingContext is not implemented for CommitMultiTree") + return db } func (db *CommitMultiTree) Snapshot(height uint64, protoWriter protoio.Writer) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("snapshotting has not been implemented yet") } func (db *CommitMultiTree) PruneSnapshotHeight(height int64) { - //TODO implement me - panic("implement me") + db.logger.Warn("PruneSnapshotHeight is not implemented for CommitMultiTree") } func (db *CommitMultiTree) SetSnapshotInterval(snapshotInterval uint64) { - //TODO implement me - panic("implement me") + db.logger.Warn("SetSnapshotInterval is not implemented for CommitMultiTree") } func (db *CommitMultiTree) Restore(height uint64, format uint32, protoReader protoio.Reader) (snapshottypes.SnapshotItem, error) { - //TODO implement me - panic("implement me") + return snapshottypes.SnapshotItem{}, fmt.Errorf("restoring from snapshot has not been implemented yet") } func (db *CommitMultiTree) MountStoreWithDB(key storetypes.StoreKey, typ storetypes.StoreType, _ dbm.DB) { @@ -238,18 +233,15 @@ func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.Sto } func (db *CommitMultiTree) LoadLatestVersionAndUpgrade(upgrades *storetypes.StoreUpgrades) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("LoadLatestVersionAndUpgrade has not been implemented yet") } func (db *CommitMultiTree) LoadVersionAndUpgrade(ver int64, upgrades *storetypes.StoreUpgrades) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("LoadVersionAndUpgrade has not been implemented yet") } func (db *CommitMultiTree) LoadVersion(ver int64) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("LoadVersion has not been implemented yet") } func (db *CommitMultiTree) SetInterBlockCache(cache storetypes.MultiStorePersistentCache) { @@ -257,8 +249,7 @@ func (db *CommitMultiTree) SetInterBlockCache(cache storetypes.MultiStorePersist } func (db *CommitMultiTree) SetInitialVersion(version int64) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("SetInitialVersion has not been implemented yet") } func (db *CommitMultiTree) SetIAVLCacheSize(size int) { @@ -271,18 +262,16 @@ func (db *CommitMultiTree) SetIAVLSyncPruning(sync bool) { } func (db *CommitMultiTree) RollbackToVersion(version int64) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("RollbackToVersion has not been implemented yet") } func (db *CommitMultiTree) ListeningEnabled(key storetypes.StoreKey) bool { - //TODO implement me - panic("implement me") + db.logger.Warn("ListeningEnabled is not implemented for CommitMultiTree") + return false } func (db *CommitMultiTree) AddListeners(keys []storetypes.StoreKey) { - //TODO implement me - panic("implement me") + db.logger.Warn("AddListeners is not implemented for CommitMultiTree") } func (db *CommitMultiTree) PopStateCache() []*storetypes.StoreKVPair { @@ -291,8 +280,7 @@ func (db *CommitMultiTree) PopStateCache() []*storetypes.StoreKVPair { } func (db *CommitMultiTree) SetMetrics(metrics metrics.StoreMetrics) { - //TODO implement me - panic("implement me") + db.logger.Warn("SetMetrics is not implemented for CommitMultiTree") } func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, error) { diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index 1d6005c9a689..c958af16347e 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -114,8 +114,7 @@ func (c *CommitTree) commit() (storetypes.CommitID, error) { } func (c *CommitTree) LastCommitID() storetypes.CommitID { - //TODO implement me - panic("implement me") + return c.lastCommitId } func (c *CommitTree) WorkingHash() []byte { @@ -155,19 +154,14 @@ func (c *CommitTree) WorkingHash() []byte { return hash } -func (c *CommitTree) SetPruning(pruningtypes.PruningOptions) { - //TODO implement me - panic("implement me") -} +func (c *CommitTree) SetPruning(pruningtypes.PruningOptions) {} func (c *CommitTree) GetPruning() pruningtypes.PruningOptions { - //TODO implement me - panic("implement me") + return pruningtypes.NewPruningOptions(pruningtypes.PruningDefault) } func (c *CommitTree) GetStoreType() storetypes.StoreType { - //TODO implement me - panic("implement me") + return storetypes.StoreTypeIAVL } func (c *CommitTree) CacheWrap() storetypes.CacheWrap { @@ -180,33 +174,45 @@ func (c *CommitTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) } func (c *CommitTree) Get(key []byte) []byte { - //TODO implement me - panic("implement me") + if c.root == nil { + return nil + } + + root, err := c.root.Resolve() + if err != nil { + panic(err) + } + + value, _, err := root.Get(key) + if err != nil { + panic(err) + } + + return value } func (c *CommitTree) Has(key []byte) bool { - //TODO implement me - panic("implement me") + return c.Get(key) != nil } func (c *CommitTree) Set(key, value []byte) { - //TODO implement me - panic("implement me") + tree := c.CacheWrap().(*Tree) + tree.Set(key, value) + tree.Write() } func (c *CommitTree) Delete(key []byte) { - //TODO implement me - panic("implement me") + tree := c.CacheWrap().(*Tree) + tree.Delete(key) + tree.Write() } func (c *CommitTree) Iterator(start, end []byte) storetypes.Iterator { - //TODO implement me - panic("implement me") + return NewIterator(start, end, true, c.root, c.zeroCopy) } func (c *CommitTree) ReverseIterator(start, end []byte) storetypes.Iterator { - //TODO implement me - panic("implement me") + return NewIterator(start, end, false, c.root, c.zeroCopy) } func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, error) { diff --git a/iavl/tree.go b/iavl/tree.go index 53e50aad8a9d..958e0c586db8 100644 --- a/iavl/tree.go +++ b/iavl/tree.go @@ -4,6 +4,7 @@ import ( io "io" corestore "cosmossdk.io/core/store" + storetypes "cosmossdk.io/store/types" ) @@ -50,8 +51,7 @@ func (tree *Tree) GetStoreType() storetypes.StoreType { } func (tree *Tree) CacheWrap() storetypes.CacheWrap { - //TODO implement me - panic("implement me") + return NewTree(tree, tree.updateBatch.Version, tree.zeroCopy) } func (tree *Tree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { diff --git a/iavl/tree_test.go b/iavl/tree_test.go index b5e3b72c1914..c22351c7eb68 100644 --- a/iavl/tree_test.go +++ b/iavl/tree_test.go @@ -3,7 +3,6 @@ package iavlx import ( "bytes" "fmt" - "log/slog" "os" "runtime/debug" "testing" @@ -21,55 +20,55 @@ func TestBasicTest(t *testing.T) { dir, err := os.MkdirTemp("", "iavlx") require.NoError(t, err) defer os.RemoveAll(dir) - commitTree, err := NewCommitTree(dir, Options{}, slog.Default()) + commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger()) require.NoError(t, err) - tree := commitTree.Branch() - require.NoError(t, tree.Set([]byte{0}, []byte{1})) + tree := commitTree.CacheWrap().(*Tree) + tree.Set([]byte{0}, []byte{1}) // renderTree(t, tree) - val, err := tree.Get([]byte{0}) + val := tree.Get([]byte{0}) require.NoError(t, err) require.Equal(t, []byte{1}, val) - require.NoError(t, tree.Set([]byte{1}, []byte{2})) + tree.Set([]byte{1}, []byte{2}) //renderTree(t, tree) - val, err = tree.Get([]byte{0}) + val = tree.Get([]byte{0}) require.NoError(t, err) require.Equal(t, []byte{1}, val) - val, err = tree.Get([]byte{1}) + val = tree.Get([]byte{1}) require.NoError(t, err) require.Equal(t, []byte{2}, val) - require.NoError(t, tree.Set([]byte{2}, []byte{3})) + tree.Set([]byte{2}, []byte{3}) //renderTree(t, tree) - val, err = tree.Get([]byte{0}) + val = tree.Get([]byte{0}) require.NoError(t, err) require.Equal(t, []byte{1}, val) - val, err = tree.Get([]byte{1}) + val = tree.Get([]byte{1}) require.NoError(t, err) require.Equal(t, []byte{2}, val) - val, err = tree.Get([]byte{2}) + val = tree.Get([]byte{2}) require.NoError(t, err) require.Equal(t, []byte{3}, val) - val, err = tree.Get([]byte{3}) + val = tree.Get([]byte{3}) require.NoError(t, err) require.Nil(t, val) - require.NoError(t, tree.Delete([]byte{1})) + tree.Delete([]byte{1}) //renderTree(t, tree) - val, err = tree.Get([]byte{1}) + val = tree.Get([]byte{1}) require.NoError(t, err) require.Nil(t, val) - require.NoError(t, commitTree.Apply(tree)) - hash, err := commitTree.Commit() + tree.Write() + commitId := commitTree.Commit() require.NoError(t, err) - require.NotNil(t, hash) - t.Logf("committed with root hash: %X", hash) + require.NotNil(t, commitId) + t.Logf("committed with root commitId: %X", commitId) require.NoError(t, commitTree.Close()) } @@ -118,7 +117,7 @@ func testIAVLXSims(t *rapid.T) { ChangesetMaxTarget: 1, CompactAfterVersions: 0, ReaderUpdateInterval: 1, - }, slog.Default()) + }, sdklog.NewNopLogger()) require.NoError(t, err, "failed to create iavlx tree") simMachine := &SimMachine{ treeV1: treeV1, @@ -178,9 +177,9 @@ func (s *SimMachine) set(t *rapid.T) { // set in both trees updated, errV1 := s.treeV1.Set(key, value) require.NoError(t, errV1, "failed to set key in V1 tree") - branch := s.treeV2.Branch() - require.NoError(t, branch.Set(key, value), "failed to set key in V2 tree") - require.NoError(t, s.treeV2.Apply(branch), "failed to apply batch to V2 tree") + branch := s.treeV2.CacheWrap().(*Tree) + branch.Set(key, value) + branch.Write() //require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees") if updated { require.NotNil(t, s.existingKeys[string(key)], "key shouldn't have been marked as updated") @@ -197,8 +196,7 @@ func (s *SimMachine) get(t *rapid.T) { var key = s.selectKey(t) valueV1, errV1 := s.treeV1.Get(key) require.NoError(t, errV1, "failed to get key from V1 tree") - valueV2, errV2 := s.treeV2.Branch().Get(key) - require.NoError(t, errV2, "failed to get key from V2 tree") + valueV2 := s.treeV2.CacheWrap().(*Tree).Get(key) require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") expectedValue, found := s.existingKeys[string(key)] if found { @@ -224,9 +222,9 @@ func (s *SimMachine) delete(t *rapid.T) { // delete in both trees _, removedV1, errV1 := s.treeV1.Remove(key) require.NoError(t, errV1, "failed to remove key from V1 tree") - branch := s.treeV2.Branch() - require.NoError(t, branch.Delete(key), "failed to remove key from V2 tree") - require.NoError(t, s.treeV2.Apply(branch), "failed to apply batch to V2 tree") + branch := s.treeV2.CacheWrap().(*Tree) + branch.Delete(key) + branch.Write() //require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees") // TODO v1 & v2 have slightly different behaviors for the value returned on removal. We should re-enable this and check. //if valueV1 == nil || len(valueV1) == 0 { @@ -258,7 +256,7 @@ func (s *SimMachine) Iterate(t *rapid.T) { func (s *SimMachine) Commit(t *rapid.T) { hash1, _, err := s.treeV1.SaveVersion() require.NoError(t, err, "failed to save version in V1 tree") - hash2, err := s.treeV2.Commit() + commitId2 := s.treeV2.Commit() require.NoError(t, err, "failed to save version in V2 tree") //s.debugDump(t) err = VerifyTree(s.treeV2) @@ -278,10 +276,7 @@ func (s *SimMachine) Commit(t *rapid.T) { // require.NoError(t, os.WriteFile("wal_dump.txt", buf.Bytes(), 0o644)) //} require.NoError(t, err, "failed to verify V2 tree") - if !bytes.Equal(hash1, hash2) { - t.Logf("WARNING: hash mismatch between V1 and V2 trees: %X vs %X", hash1, hash2) - } - require.Equal(t, hash1, hash2, "hash mismatch between V1 and V2 trees") + require.Equal(t, hash1, commitId2.Hash, "hash mismatch between V1 and V2 trees") //require.Equal(t, v1, v2, "version mismatch between V1 and V2 trees") } @@ -292,8 +287,7 @@ func (s *SimMachine) debugDump(t *rapid.T) { iavl.WriteDOTGraph(graph1, s.treeV1.ImmutableTree, nil) t.Logf("V1 tree:\n%s", graph1.String()) //renderTree(t, s.treeV2.Branch()) - iter2, err := s.treeV2.Branch().Iterator(nil, nil) - require.NoError(t, err, "failed to create iterator for V2 tree") + iter2 := s.treeV2.CacheWrap().(*Tree).Iterator(nil, nil) s.debugDumpTree(t, iter2) } @@ -334,8 +328,7 @@ func (s *SimMachine) debugDumpTree(t *rapid.T, iter corestore.Iterator) { func (s *SimMachine) compareIterators(t *rapid.T, start, end []byte, ascending bool) { iter1, err1 := s.treeV1.Iterator(start, end, ascending) require.NoError(t, err1, "failed to create iterator for V1 tree") - iter2, err2 := s.treeV2.Branch().Iterator(start, end) - require.NoError(t, err2, "failed to create iterator for V2 tree") + iter2 := s.treeV2.CacheWrap().(*Tree).Iterator(start, end) compareIteratorsAtVersion(t, iter1, iter2) } @@ -364,39 +357,3 @@ func compareIteratorsAtVersion(t *rapid.T, iterV1 corestore.Iterator, iterV2 cor iterV2.Next() } } - -func TestSimpleOperations(t *testing.T) { - batch := &KVUpdateBatch{Version: 1} - tree := NewTree(nil, batch, false) - require.NoError(t, tree.Set([]byte{1}, []byte{1})) - renderTree(t, tree) - batch.Version = 2 - require.NoError(t, tree.Set([]byte{2}, []byte{2})) - renderTree(t, tree) - batch.Version = 3 - require.NoError(t, tree.Set([]byte{1}, []byte{2})) - renderTree(t, tree) - batch.Version = 4 - require.NoError(t, tree.Set([]byte{3}, []byte{3})) - renderTree(t, tree) -} - -func TestEx1(t *testing.T) { - batch := &KVUpdateBatch{Version: 1} - tree := NewTree(nil, batch, false) - require.NoError(t, tree.Set([]byte{1}, []byte{1})) - require.NoError(t, tree.Set([]byte{2}, []byte{2})) - require.NoError(t, tree.Set([]byte{3}, []byte{3})) - require.NoError(t, tree.Set([]byte{1}, []byte{2})) - require.NoError(t, tree.Set([]byte{3}, []byte{4})) - renderTree(t, tree) -} - -func TestEx2(t *testing.T) { - batch := &KVUpdateBatch{Version: 1} - tree := NewTree(nil, batch, false) - require.NoError(t, tree.Set([]byte{3}, []byte{4})) - require.NoError(t, tree.Set([]byte{2}, []byte{2})) - require.NoError(t, tree.Set([]byte{1}, []byte{2})) - renderTree(t, tree) -} From 29edd5458c1ee32e20f8b7fe2c7fb7c8f29a9bfd Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 20 Oct 2025 20:03:00 -0400 Subject: [PATCH 07/87] WIP on system tests --- iavl/changeset.go | 16 ++++++++++++++++ iavl/commit_multi_tree.go | 33 ++++++++++++++++++++++++++++----- iavl/commit_tree.go | 18 ++++++++++++++++-- iavl/tree.go | 5 +++++ iavl/tree_store.go | 8 ++++++++ iavl/tree_test.go | 4 ++-- 6 files changed, 75 insertions(+), 9 deletions(-) diff --git a/iavl/changeset.go b/iavl/changeset.go index 3eac56a32608..22875847a76c 100644 --- a/iavl/changeset.go +++ b/iavl/changeset.go @@ -397,3 +397,19 @@ func (cr *Changeset) TotalBytes() int { func (cr *Changeset) HasOrphans() bool { return cr.info.LeafOrphans > 0 || cr.info.BranchOrphans > 0 } + +func (cr *Changeset) ResolveRoot(version uint32) (*NodePointer, error) { + startVersion := cr.info.StartVersion + endVersion := startVersion + uint32(cr.versionsData.Count()) - 1 + if version < startVersion || version > endVersion { + return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, startVersion, endVersion) + } + vi, err := cr.getVersionInfo(version) + if err != nil { + return nil, err + } + return &NodePointer{ + id: vi.RootID, + store: cr, + }, nil +} diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index 6177c9ed508b..a8ede7953652 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -4,6 +4,7 @@ import ( "fmt" io "io" "os" + "path/filepath" "runtime" "cosmossdk.io/log" @@ -130,11 +131,30 @@ func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes if version == 0 { version = int64(db.version) } - if uint64(version) == db.version { - // TODO we actually want to cache wrap the latest saved version, not the working version - return db.CacheMultiStore(), nil + + mt := &MultiTree{ + latestVersion: version, + treesByKey: db.treesByKey, // share the map + trees: make([]storetypes.CacheKVStore, len(db.trees)), + } + + for i, tree := range db.trees { + typ := db.storeTypes[i] + switch typ { + case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: + var err error + mt.trees[i], err = tree.(*CommitTree).GetImmutable(version) + if err != nil { + return nil, fmt.Errorf("failed to create cache multi store for tree %s at version %d: %w", db.treeKeys[i].Name(), version, err) + } + case storetypes.StoreTypeTransient, storetypes.StoreTypeMemory: + mt.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) + default: + return nil, fmt.Errorf("unsupported store type: %s", typ.String()) + } } - return nil, fmt.Errorf("checking out historical versions has not been implemented yet") + + return mt, nil } func (db *CommitMultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { @@ -208,7 +228,10 @@ func (db *CommitMultiTree) LoadLatestVersion() error { func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.StoreType) (storetypes.CommitKVStore, error) { switch typ { case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: - dir := fmt.Sprintf("%s/%s", db.dir, key.Name()) + dir := filepath.Join(db.dir, key.Name()) + if _, err := os.Stat(dir); !os.IsNotExist(err) { + return nil, fmt.Errorf("store directory %s already exists, reloading isn't supported yet", dir) + } err := os.MkdirAll(dir, 0o755) if err != nil { return nil, fmt.Errorf("failed to create store dir %s: %w", dir, err) diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index c958af16347e..d7cc1b6931f1 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -60,8 +60,6 @@ func (c *CommitTree) ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KV c.walChan <- updateBatch.Updates } - // TODO prevent further writes to the branch tree - return nil } @@ -288,6 +286,22 @@ func (c *CommitTree) startEvict(evictVersion uint32) { }() } +func (c *CommitTree) GetImmutable(version int64) (storetypes.CacheKVStore, error) { + var rootPtr *NodePointer + if version == c.lastCommitId.Version { + rootPtr = c.root + } else { + var err error + rootPtr, err = c.store.ResolveRoot(uint32(version)) + if err != nil { + return nil, err + } + } + return &Tree{ + root: rootPtr, + }, nil +} + func (c *CommitTree) Close() error { if c.walChan != nil { close(c.walChan) diff --git a/iavl/tree.go b/iavl/tree.go index 958e0c586db8..8ec7344797f7 100644 --- a/iavl/tree.go +++ b/iavl/tree.go @@ -41,6 +41,7 @@ func (tree *Tree) ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KVUpd panic("cannot apply changes: root has changed") } tree.root = newRoot + tree.origRoot = newRoot tree.updateBatch.Updates = append(tree.updateBatch.Updates, updateBatch.Updates...) tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, updateBatch.Orphans...) return nil @@ -60,6 +61,9 @@ func (tree *Tree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) st } func (tree *Tree) Write() { + if tree.parent == nil { + panic("cannot write: tree is immutable") + } err := tree.parent.ApplyChanges(tree.origRoot, tree.root, tree.updateBatch) if err != nil { panic(err) @@ -67,6 +71,7 @@ func (tree *Tree) Write() { tree.updateBatch.Updates = nil tree.updateBatch.Orphans = nil tree.root = tree.parent.Root() + tree.origRoot = tree.root } func (tree *Tree) Get(key []byte) []byte { diff --git a/iavl/tree_store.go b/iavl/tree_store.go index 032a3e60abdd..0e33f5cb46ed 100644 --- a/iavl/tree_store.go +++ b/iavl/tree_store.go @@ -164,6 +164,14 @@ func (ts *TreeStore) Resolve(nodeId NodeID, _ uint32) (Node, error) { return cs.Resolve(nodeId, 0) } +func (ts *TreeStore) ResolveRoot(version uint32) (*NodePointer, error) { + cs := ts.getChangesetForVersion(version) + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", version) + } + return cs.ResolveRoot(version) +} + func (ts *TreeStore) SavedVersion() uint32 { return ts.savedVersion.Load() } diff --git a/iavl/tree_test.go b/iavl/tree_test.go index c22351c7eb68..6686c4d7a671 100644 --- a/iavl/tree_test.go +++ b/iavl/tree_test.go @@ -152,7 +152,7 @@ func (s *SimMachine) Check(t *rapid.T) { } func (s *SimMachine) UpdateN(t *rapid.T) { - n := rapid.IntRange(1, 1000).Draw(t, "n") + n := rapid.IntRange(1, 5000).Draw(t, "n") for i := 0; i < n; i++ { del := rapid.Bool().Draw(t, "del") if del { @@ -164,7 +164,7 @@ func (s *SimMachine) UpdateN(t *rapid.T) { } func (s *SimMachine) GetN(t *rapid.T) { - n := rapid.IntRange(1, 1000).Draw(t, "n") + n := rapid.IntRange(1, 5000).Draw(t, "n") for i := 0; i < n; i++ { s.get(t) } From aa26a92b10aa9d19e9d3e311c5f5b888afe9c078 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:49:44 -0700 Subject: [PATCH 08/87] i think thats everything? i guess? --- iavl/branch_layout.go | 8 +++--- iavl/changeset.go | 12 +++++++-- iavl/changeset_writer.go | 24 ++++++++++++----- iavl/compactor.go | 56 +++++++--------------------------------- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/iavl/branch_layout.go b/iavl/branch_layout.go index 72d355b85ed7..a90c9c1645fc 100644 --- a/iavl/branch_layout.go +++ b/iavl/branch_layout.go @@ -12,13 +12,15 @@ func init() { } const ( - SizeBranch = 72 + SizeBranch = 88 ) type BranchLayout struct { Id NodeID - Left NodeRef - Right NodeRef + Left NodeID + LeftOffset uint32 // absolute offset + Right NodeID + RightOffset uint32 // absolute offset KeyOffset uint32 Height uint8 Size uint32 // TODO 5 bytes? diff --git a/iavl/changeset.go b/iavl/changeset.go index 3eac56a32608..b47ee10b16ba 100644 --- a/iavl/changeset.go +++ b/iavl/changeset.go @@ -170,6 +170,14 @@ func (cr *Changeset) resolveBranchWithIdx(nodeId NodeID, fileIdx uint32) (Branch } } +func (cr *Changeset) resolveNodeID(id NodeID) *NodePointer { + return &NodePointer{ + id: id, + store: cr.treeStore.getChangesetForVersion(uint32(id.Version())), + } +} + +// TODO(technicallyty): remove this? func (cr *Changeset) resolveNodeRef(nodeRef NodeRef, selfIdx uint32) *NodePointer { if nodeRef.IsNodeID() { id := nodeRef.AsNodeID() @@ -231,8 +239,8 @@ func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { return nil, err } - leftPtr := cr.resolveNodeRef(layout.Left, actualIdx) - rightPtr := cr.resolveNodeRef(layout.Right, actualIdx) + leftPtr := cr.resolveNodeID(layout.Left) + rightPtr := cr.resolveNodeID(layout.Right) return &BranchPersisted{ layout: layout, diff --git a/iavl/changeset_writer.go b/iavl/changeset_writer.go index 689d258a7061..e1341e6c2d62 100644 --- a/iavl/changeset_writer.go +++ b/iavl/changeset_writer.go @@ -157,15 +157,27 @@ func (cs *ChangesetWriter) writeBranch(np *NodePointer, node *MemNode) error { } } - // now write parent - parentIdx := int64(cs.branchesData.Count() + 1) // +1 to account for the node being written - leftRef := cs.createNodeRef(parentIdx, node.left) - rightRef := cs.createNodeRef(parentIdx, node.right) + leftVersion := node.left.id.Version() + rightVersion := node.right.id.Version() + + var leftOffset uint32 + var rightOffset uint32 + + // If the child node is in the same changeset, store its 1-based file offset. + // fileIdx is already 1-based (set to Count() after append), and 0 means no offset. + if leftVersion >= uint64(cs.StartVersion()) { + leftOffset = node.left.fileIdx + } + if rightVersion >= uint64(cs.StartVersion()) { + rightOffset = node.right.fileIdx + } layout := BranchLayout{ Id: np.id, - Left: leftRef, - Right: rightRef, + Left: node.left.id, + Right: node.right.id, + LeftOffset: leftOffset, + RightOffset: rightOffset, KeyOffset: keyOffset, Height: node.height, Size: uint32(node.size), // TODO check overflow diff --git a/iavl/compactor.go b/iavl/compactor.go index dbba338a972c..4c715f7b662a 100644 --- a/iavl/compactor.go +++ b/iavl/compactor.go @@ -32,6 +32,7 @@ type Compactor struct { leafOffsetRemappings map[uint32]uint32 keyCache map[string]uint32 + offsetCache map[NodeID]uint32 // Running totals across all processed changesets leafOrphanCount uint32 @@ -83,6 +84,7 @@ func NewCompacter(logger *slog.Logger, reader *Changeset, opts CompactOptions, s versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile), keyCache: make(map[string]uint32), leafOffsetRemappings: make(map[uint32]uint32), + offsetCache: make(map[NodeID]uint32), } // Process first changeset immediately @@ -162,7 +164,8 @@ func (c *Compactor) processChangeset(reader *Changeset) error { } oldLeafFileIdx := leafStartOffset + j - c.leafOffsetRemappings[oldLeafFileIdx] = uint32(c.leavesWriter.Count()) - 1 + c.leafOffsetRemappings[oldLeafFileIdx] = uint32(c.leavesWriter.Count()) + c.offsetCache[id] = uint32(c.leavesWriter.Count()) } newBranchStartIdx := uint32(0) @@ -191,24 +194,11 @@ func (c *Compactor) processChangeset(reader *Changeset) error { newBranchEndIdx = id.Index() newBranchCount++ - var err error - left := branch.Left - branch.Left, err = c.updateNodeRef(reader, left, skippedBranches) - if err != nil { - c.logger.Error("failed to update left ref", - "branchId", id, - "branchOrphanVersion", branch.OrphanVersion, - "leftRef", left) - return fmt.Errorf("failed to update left ref for branch %s: %w", id, err) + if newLeftOffset, ok := c.offsetCache[branch.Left]; ok { + branch.LeftOffset = newLeftOffset } - right := branch.Right - branch.Right, err = c.updateNodeRef(reader, right, skippedBranches) - if err != nil { - c.logger.Error("failed to update right ref", - "branchId", id, - "branchOrphanVersion", branch.OrphanVersion, - "rightRef", right) - return fmt.Errorf("failed to update right ref for branch %s: %w", id, err) + if newRightOffset, ok := c.offsetCache[branch.Right]; ok { + branch.RightOffset = newRightOffset } if c.compactWAL { @@ -229,10 +219,11 @@ func (c *Compactor) processChangeset(reader *Changeset) error { branch.KeyOffset += kvOffsetDelta } - err = c.branchesWriter.Append(&branch) + err := c.branchesWriter.Append(&branch) if err != nil { return fmt.Errorf("failed to append branch %s: %w", id, err) } + c.offsetCache[id] = uint32(c.branchesWriter.Count()) } verInfo = VersionInfo{ @@ -307,33 +298,6 @@ func (c *Compactor) Seal() (*Changeset, error) { return cs, nil } -func (c *Compactor) updateNodeRef(reader *Changeset, ref NodeRef, skipped int) (NodeRef, error) { - if ref.IsNodeID() { - return ref, nil - } - relPtr := ref.AsRelativePointer() - if relPtr.IsLeaf() { - oldOffset := relPtr.Offset() - newOffset, ok := c.leafOffsetRemappings[uint32(oldOffset)] - if !ok { - // Debug: look up the orphaned leaf - oldLeaf := reader.leavesData.UnsafeItem(uint32(oldOffset) - 1) - c.logger.Error("leaf remapping failed - orphaned leaf still referenced", - "leafOffset", oldOffset, - "leafId", oldLeaf.Id, - "leafOrphanVersion", oldLeaf.OrphanVersion, - "remappings", c.leafOffsetRemappings) - return 0, fmt.Errorf("failed to find remapping for leaf offset %d", oldOffset) - } - return NodeRef(NewNodeRelativePointer(true, int64(newOffset))), nil - } else { - // branch nodes we reduce by the number of skipped nodes - oldOffset := relPtr.Offset() - newOffset := oldOffset - int64(skipped) - return NodeRef(NewNodeRelativePointer(false, newOffset)), nil - } -} - func (c *Compactor) Abort() error { err := c.files.Close() if err != nil { From 80b98c6f96af891f1f10cfd4cc07431a756cbf74 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 21 Oct 2025 12:29:28 -0400 Subject: [PATCH 09/87] fix bug with multi-store isolation --- iavl/commit_multi_tree.go | 13 ++++++++++--- iavl/multi_tree.go | 9 ++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index a8ede7953652..6db107a7e66a 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -162,7 +162,14 @@ func (db *CommitMultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { } func (db *CommitMultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { - return db.trees[db.treesByKey[key]] + index, ok := db.treesByKey[key] + if !ok { + panic(fmt.Sprintf("store not found for key: %s (key type: %T)", key.Name(), key)) + } + if index >= len(db.trees) { + panic(fmt.Sprintf("store index %d out of bounds for key %s (trees length: %d)", index, key.Name(), len(db.trees))) + } + return db.trees[index] } func (db *CommitMultiTree) TracingEnabled() bool { @@ -199,10 +206,10 @@ func (db *CommitMultiTree) MountStoreWithDB(key storetypes.StoreKey, typ storety if _, exists := db.treesByKey[key]; exists { panic(fmt.Sprintf("store with key %s already mounted", key.Name())) } - index := len(db.trees) + index := len(db.treeKeys) + db.treesByKey[key] = index db.treeKeys = append(db.treeKeys, key) db.storeTypes = append(db.storeTypes, typ) - db.treesByKey[key] = index } func (db *CommitMultiTree) GetCommitStore(key storetypes.StoreKey) storetypes.CommitStore { diff --git a/iavl/multi_tree.go b/iavl/multi_tree.go index 451dd9c880f6..44ee223ac4ee 100644 --- a/iavl/multi_tree.go +++ b/iavl/multi_tree.go @@ -52,7 +52,14 @@ func (t *MultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { } func (t *MultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { - return t.trees[t.treesByKey[key]] + index, ok := t.treesByKey[key] + if !ok { + panic(fmt.Sprintf("store not found for key: %s (key type: %T)", key.Name(), key)) + } + if index >= len(t.trees) { + panic(fmt.Sprintf("store index %d out of bounds for key %s (trees length: %d)", index, key.Name(), len(t.trees))) + } + return t.trees[index] } func (t *MultiTree) TracingEnabled() bool { From fbf7209b0c000a7e73879cd4d044a8b5fa699f1a Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 21 Oct 2025 12:37:15 -0400 Subject: [PATCH 10/87] attempting to fix cachewrap write behavior --- iavl/commit_tree.go | 4 ++-- iavl/tree.go | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index d7cc1b6931f1..184e5c0046d2 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -37,11 +37,11 @@ type CommitTree struct { workingCommitId storetypes.CommitID } -func (c *CommitTree) Root() *NodePointer { +func (c *CommitTree) getRoot() *NodePointer { return c.root } -func (c *CommitTree) ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { +func (c *CommitTree) applyChangesToParent(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { // TODO check channel errors c.writeMutex.Lock() defer c.writeMutex.Unlock() diff --git a/iavl/tree.go b/iavl/tree.go index 8ec7344797f7..46e7779bda76 100644 --- a/iavl/tree.go +++ b/iavl/tree.go @@ -17,12 +17,12 @@ type Tree struct { } type parentTree interface { - Root() *NodePointer - ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error + getRoot() *NodePointer + applyChangesToParent(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error } func NewTree(parent parentTree, stagedVersion uint32, zeroCopy bool) *Tree { - root := parent.Root() + root := parent.getRoot() return &Tree{ parent: parent, root: root, @@ -32,16 +32,15 @@ func NewTree(parent parentTree, stagedVersion uint32, zeroCopy bool) *Tree { } } -func (tree *Tree) Root() *NodePointer { +func (tree *Tree) getRoot() *NodePointer { return tree.root } -func (tree *Tree) ApplyChanges(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { +func (tree *Tree) applyChangesToParent(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { if tree.root != origRoot { panic("cannot apply changes: root has changed") } tree.root = newRoot - tree.origRoot = newRoot tree.updateBatch.Updates = append(tree.updateBatch.Updates, updateBatch.Updates...) tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, updateBatch.Orphans...) return nil @@ -64,13 +63,13 @@ func (tree *Tree) Write() { if tree.parent == nil { panic("cannot write: tree is immutable") } - err := tree.parent.ApplyChanges(tree.origRoot, tree.root, tree.updateBatch) + err := tree.parent.applyChangesToParent(tree.origRoot, tree.root, tree.updateBatch) if err != nil { panic(err) } tree.updateBatch.Updates = nil tree.updateBatch.Orphans = nil - tree.root = tree.parent.Root() + tree.root = tree.parent.getRoot() tree.origRoot = tree.root } @@ -140,5 +139,5 @@ func (tree *Tree) ReverseIterator(start, end []byte) corestore.Iterator { return NewIterator(start, end, false, tree.root, tree.zeroCopy) } -var _ storetypes.CacheKVStore = &Tree{} -var _ parentTree = &Tree{} +var _ storetypes.CacheKVStore = (*Tree)(nil) +var _ parentTree = (*Tree)(nil) From 13aa9fae42e9eff8695dd21344a4d656f2259199 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 21 Oct 2025 13:08:22 -0400 Subject: [PATCH 11/87] fixing bugs and fixing working hash implementation --- iavl/commit_multi_tree.go | 97 ++++++++++++++++++++++++----------- iavl/commit_tree.go | 105 ++++++++++++++++++++++---------------- 2 files changed, 129 insertions(+), 73 deletions(-) diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index 6db107a7e66a..9b6fc8950be6 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -1,6 +1,7 @@ package iavlx import ( + "bytes" "fmt" io "io" "os" @@ -29,10 +30,11 @@ type CommitMultiTree struct { storeTypes []storetypes.StoreType // store types by tree index treesByKey map[storetypes.StoreKey]int // index of the trees by name - version uint64 - lastCommitId storetypes.CommitID - commitPool pond.ResultPool[storetypes.CommitID] - workingCommitId *storetypes.CommitID + version uint64 + lastCommitId storetypes.CommitID + commitPool pond.ResultPool[[]byte] + workingCommitInfo *storetypes.CommitInfo + workingHash []byte } func (db *CommitMultiTree) LastCommitID() storetypes.CommitID { @@ -40,16 +42,13 @@ func (db *CommitMultiTree) LastCommitID() storetypes.CommitID { } func (db *CommitMultiTree) WorkingHash() []byte { + // NOTE: this may invoke some hash recomputation each time even if there is no change taskGroup := db.commitPool.NewGroup() stagedVersion := db.version + 1 for _, tree := range db.trees { t := tree - taskGroup.Submit(func() storetypes.CommitID { - hash := t.WorkingHash() - return storetypes.CommitID{ - Version: int64(stagedVersion), - Hash: hash, - } + taskGroup.Submit(func() []byte { + return t.WorkingHash() }) } hashes, err := taskGroup.Wait() @@ -58,41 +57,81 @@ func (db *CommitMultiTree) WorkingHash() []byte { } commitInfo := &storetypes.CommitInfo{} + commitInfo.StoreInfos = make([]storetypes.StoreInfo, len(db.treeKeys)) for i, treeKey := range db.treeKeys { commitInfo.StoreInfos[i] = storetypes.StoreInfo{ - Name: treeKey.Name(), - CommitId: hashes[i], + Name: treeKey.Name(), + CommitId: storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hashes[i], + }, } } - db.workingCommitId = &storetypes.CommitID{ - Version: int64(stagedVersion), - Hash: commitInfo.Hash(), - } - return db.workingCommitId.Hash + db.workingCommitInfo = commitInfo + hash := commitInfo.Hash() + db.workingHash = hash + return hash } func (db *CommitMultiTree) Commit() storetypes.CommitID { - // comput hash (if not done already) - db.WorkingHash() - - // actually commit all trees + // NOTE: this function is maybe unnecessarily complex because the SDK has both WorkingHash and Commit methods + // and we're trying to avoid recomputing the hash + // so we check if we already have a hash that was computed in WorkingHash that hasn't changed to avoid recomputation + // in the future we should evaluate if there is any need to retain both WorkingHash and Commit methods separately taskGroup := db.commitPool.NewGroup() for _, tree := range db.trees { t := tree - taskGroup.Submit(func() storetypes.CommitID { - return t.Commit() + taskGroup.Submit(func() []byte { + commitId := t.Commit() + return commitId.Hash }) } - _, err := taskGroup.Wait() + + hashes, err := taskGroup.Wait() if err != nil { panic(fmt.Errorf("failed to commit trees: %w", err)) } + stagedVersion := db.version + 1 + commitInfo := db.workingCommitInfo + var hash []byte + if commitInfo == nil { + commitInfo = &storetypes.CommitInfo{} + commitInfo.StoreInfos = make([]storetypes.StoreInfo, len(db.treeKeys)) + for i, treeKey := range db.treeKeys { + commitInfo.StoreInfos[i] = storetypes.StoreInfo{ + Name: treeKey.Name(), + CommitId: storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hashes[i], + }, + } + } + hash = commitInfo.Hash() + } else { + hashChanged := false + for i, storeInfo := range commitInfo.StoreInfos { + if !bytes.Equal(storeInfo.CommitId.Hash, hashes[i]) { + hashChanged = true + commitInfo.StoreInfos[i].CommitId.Hash = hashes[i] + } + } + if !hashChanged { + hash = db.workingHash + } else { + hash = commitInfo.Hash() + } + db.workingCommitInfo = nil + db.workingHash = nil + } + db.version++ - commitId := db.workingCommitId - db.workingCommitId = nil - db.lastCommitId = *commitId - return *commitId + commitId := storetypes.CommitID{ + Version: int64(db.version), + Hash: hash, + } + db.lastCommitId = commitId + return commitId } func (db *CommitMultiTree) SetPruning(options pruningtypes.PruningOptions) { @@ -338,7 +377,7 @@ func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, er db := &CommitMultiTree{ dir: path, opts: *opts, - commitPool: pond.NewResultPool[storetypes.CommitID](runtime.NumCPU()), + commitPool: pond.NewResultPool[[]byte](runtime.NumCPU()), logger: logger, treesByKey: make(map[storetypes.StoreKey]int), } diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index 184e5c0046d2..21a879d1bc8e 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -32,9 +32,8 @@ type CommitTree struct { logger log.Logger - commitCtx *commitContext - lastCommitId storetypes.CommitID - workingCommitId storetypes.CommitID + lastCommitId storetypes.CommitID + commitCtx *commitContext } func (c *CommitTree) getRoot() *NodePointer { @@ -63,6 +62,43 @@ func (c *CommitTree) applyChangesToParent(origRoot, newRoot *NodePointer, update return nil } +func (c *CommitTree) WorkingHash() []byte { + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + return c.workingHash() +} + +func (c *CommitTree) workingHash() []byte { + // IMPORTANT: this function assumes the write lock is held + + // if we have no root, return empty hash + if c.root == nil { + c.commitCtx = nil + return emptyHash + } + + root := c.root.mem.Load() + if root != nil { + // already computed working hash + return root.hash + } + + savedVersion := c.store.SavedVersion() + stagedVersion := c.stagedVersion() + c.commitCtx = &commitContext{ + version: stagedVersion, + savedVersion: savedVersion, + } + + // compute hash and assign node IDs + hash, err := commitTraverse(c.commitCtx, c.root, 0) + if err != nil { + panic(fmt.Sprintf("failed to compute working hash: %v", err)) + } + return hash +} + func (c *CommitTree) Commit() storetypes.CommitID { commitId, err := c.commit() if err != nil { @@ -72,8 +108,15 @@ func (c *CommitTree) Commit() storetypes.CommitID { } func (c *CommitTree) commit() (storetypes.CommitID, error) { - c.WorkingHash() - commitId := c.workingCommitId + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + if c.writeWal { + close(c.walChan) + } + + // compute hash and assign node IDs + hash := c.workingHash() stagedVersion := c.stagedVersion() if c.writeWal { @@ -91,7 +134,12 @@ func (c *CommitTree) commit() (storetypes.CommitID, error) { c.reinitWalProc() } - err := c.store.SaveRoot(stagedVersion, c.root, c.commitCtx.leafNodeIdx, c.commitCtx.branchNodeIdx) + commitCtx := c.commitCtx + if commitCtx == nil { + // make sure we have a non-nil commit context + commitCtx = &commitContext{} + } + err := c.store.SaveRoot(stagedVersion, c.root, commitCtx.leafNodeIdx, commitCtx.branchNodeIdx) if err != nil { return storetypes.CommitID{}, err } @@ -105,6 +153,10 @@ func (c *CommitTree) commit() (storetypes.CommitID, error) { // cache the committed tree as the latest version c.latest.Store(c.root) c.version++ + commitId := storetypes.CommitID{ + Version: int64(stagedVersion), + Hash: hash, + } c.lastCommitId = commitId c.commitCtx = nil @@ -115,43 +167,6 @@ func (c *CommitTree) LastCommitID() storetypes.CommitID { return c.lastCommitId } -func (c *CommitTree) WorkingHash() []byte { - if c.commitCtx != nil { - return c.workingCommitId.Hash - } - - c.writeMutex.Lock() - defer c.writeMutex.Unlock() - - if c.writeWal { - close(c.walChan) - } - - var hash []byte - savedVersion := c.store.SavedVersion() - stagedVersion := c.stagedVersion() - c.commitCtx = &commitContext{ - version: stagedVersion, - savedVersion: savedVersion, - } - if c.root == nil { - hash = emptyHash - } else { - // compute hash and assign node IDs - var err error - hash, err = commitTraverse(c.commitCtx, c.root, 0) - if err != nil { - panic(fmt.Sprintf("failed to compute working hash: %v", err)) - } - } - - c.workingCommitId = storetypes.CommitID{ - Version: int64(stagedVersion), - Hash: hash, - } - return hash -} - func (c *CommitTree) SetPruning(pruningtypes.PruningOptions) {} func (c *CommitTree) GetPruning() pruningtypes.PruningOptions { @@ -318,6 +333,8 @@ type commitContext struct { leafNodeIdx uint32 } +// commitTraverse performs a post-order traversal of the tree to compute hashes and assign node IDs. +// if it is run multiple times and the tree has been mutated before being committed, node IDs will be reassigned. func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []byte, err error) { memNode := np.mem.Load() if memNode == nil { @@ -353,7 +370,7 @@ func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []by } if memNode.hash != nil { - // not sure when we would encounter this but if the hash is already computed, just return it + // hash previously computed node return memNode.hash, nil } From ca2d85ac18cf7b6bc4be19e7faafa2a9a56120e8 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:13:54 -0700 Subject: [PATCH 12/87] fix struct alignment --- iavl/branch_layout.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iavl/branch_layout.go b/iavl/branch_layout.go index a90c9c1645fc..0c9478e810a5 100644 --- a/iavl/branch_layout.go +++ b/iavl/branch_layout.go @@ -18,8 +18,8 @@ const ( type BranchLayout struct { Id NodeID Left NodeID - LeftOffset uint32 // absolute offset Right NodeID + LeftOffset uint32 // absolute offset RightOffset uint32 // absolute offset KeyOffset uint32 Height uint8 From 8b61055d84d7f216d1a1c33eec000c888f5002e2 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 21 Oct 2025 13:14:06 -0400 Subject: [PATCH 13/87] revert unneeded makefile changes --- Makefile | 71 +++++++++++++++++++++++--------------------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index d5be191b11b4..fb2c726c50c2 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ifeq ($(findstring -,$(VERSION)),) # No "-" means it's just a hash VERSION := 0.0.0-$(VERSION_RAW) endif export VERSION -export CMTVERSION := $(shell go list -m github.com/cometbft/cometbft/v2 | sed 's:.* ::') +export CMTVERSION := $(shell go list -m github.com/cometbft/cometbft | sed 's:.* ::') export COMMIT := $(shell git log -1 --format='%H') LEDGER_ENABLED ?= true BINDIR ?= $(GOPATH)/bin @@ -67,7 +67,7 @@ ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=sim \ -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" \ - -X github.com/cometbft/cometbft/v2/version.TMCoreSemVer=$(CMTVERSION) + -X github.com/cometbft/cometbft/version.TMCoreSemVer=$(CMTVERSION) # DB backend selection ifeq (cleveldb,$(findstring cleveldb,$(COSMOS_BUILD_OPTIONS))) @@ -134,15 +134,11 @@ cosmovisor: confix: $(MAKE) -C tools/confix confix -hubl: - $(MAKE) -C tools/hubl hubl - .PHONY: build build-linux-amd64 build-linux-arm64 cosmovisor confix - #? mocks: Generate mock file mocks: $(MOCKS_DIR) - @go install go.uber.org/mock/mockgen@v0.5.0 + @go install go.uber.org/mock/mockgen@v0.6.0 sh ./scripts/mockgen.sh .PHONY: mocks @@ -383,7 +379,7 @@ benchmark: ### Linting ### ############################################################################### -golangci_version=v2.3.1 +golangci_version=v2.5.0 lint-install: @echo "--> Installing golangci-lint $(golangci_version)" @@ -391,13 +387,12 @@ lint-install: lint: @echo "--> Running linter on all files" - $(MAKE) lint-install + @$(MAKE) lint-install @./scripts/go-lint-all.bash --timeout=15m - lint-fix: @echo "--> Running linter" - $(MAKE) lint-install + @$(MAKE) lint-install @./scripts/go-lint-all.bash --fix .PHONY: lint lint-fix @@ -431,52 +426,44 @@ proto-lint: proto-check-breaking: @$(protoImage) buf breaking --against $(HTTPS_GIT)#branch=main -CMT_VERSION_DIR = v1.0.1 -CMT_PROTO = v1 -CMT_URL = https://raw.githubusercontent.com/cometbft/cometbft/$(CMT_VERSION_DIR)/proto/cometbft -CMT_CRYPTO_TYPES = proto/cometbft/crypto/$(CMT_PROTO) -CMT_ABCI_TYPES = proto/cometbft/abci/$(CMT_PROTO) -CMT_TYPES = proto/cometbft/types/$(CMT_PROTO) -CMT_VERSION = proto/cometbft/version/$(CMT_PROTO) -CMT_LIBS = proto/cometbft/libs/bits/$(CMT_PROTO) -CMT_P2P = proto/cometbft/p2p/$(CMT_PROTO) +CMT_URL = https://raw.githubusercontent.com/cometbft/cometbft/v0.38.0/proto/tendermint -proto-update-comet: - @echo "Updating Protobuf dependency: downloading cometbft.$(CMT_PROTO) files from CometBFT $(CMT_VERSION_DIR)" +CMT_CRYPTO_TYPES = proto/tendermint/crypto +CMT_ABCI_TYPES = proto/tendermint/abci +CMT_TYPES = proto/tendermint/types +CMT_VERSION = proto/tendermint/version +CMT_LIBS = proto/tendermint/libs/bits +CMT_P2P = proto/tendermint/p2p + +proto-update-deps: + @echo "Updating Protobuf dependencies" @mkdir -p $(CMT_ABCI_TYPES) - @curl -fsSL $(CMT_URL)/abci/$(CMT_PROTO)/service.proto > $(CMT_ABCI_TYPES)/service.proto - @curl -fsSL $(CMT_URL)/abci/$(CMT_PROTO)/types.proto > $(CMT_ABCI_TYPES)/types.proto + @curl -sSL $(CMT_URL)/abci/types.proto > $(CMT_ABCI_TYPES)/types.proto @mkdir -p $(CMT_VERSION) - @curl -fsSL $(CMT_URL)/version/$(CMT_PROTO)/types.proto > $(CMT_VERSION)/types.proto + @curl -sSL $(CMT_URL)/version/types.proto > $(CMT_VERSION)/types.proto @mkdir -p $(CMT_TYPES) - @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/block.proto > $(CMT_TYPES)/block.proto - @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/canonical.proto > $(CMT_TYPES)/canonical.proto - @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/events.proto > $(CMT_TYPES)/events.proto - @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/evidence.proto > $(CMT_TYPES)/evidence.proto - @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/params.proto > $(CMT_TYPES)/params.proto - @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/types.proto > $(CMT_TYPES)/types.proto - @curl -fsSL $(CMT_URL)/types/$(CMT_PROTO)/validator.proto > $(CMT_TYPES)/validator.proto + @curl -sSL $(CMT_URL)/types/types.proto > $(CMT_TYPES)/types.proto + @curl -sSL $(CMT_URL)/types/evidence.proto > $(CMT_TYPES)/evidence.proto + @curl -sSL $(CMT_URL)/types/params.proto > $(CMT_TYPES)/params.proto + @curl -sSL $(CMT_URL)/types/validator.proto > $(CMT_TYPES)/validator.proto + @curl -sSL $(CMT_URL)/types/block.proto > $(CMT_TYPES)/block.proto @mkdir -p $(CMT_CRYPTO_TYPES) - @curl -fsSL $(CMT_URL)/crypto/$(CMT_PROTO)/keys.proto > $(CMT_CRYPTO_TYPES)/keys.proto - @curl -fsSL $(CMT_URL)/crypto/$(CMT_PROTO)/proof.proto > $(CMT_CRYPTO_TYPES)/proof.proto + @curl -sSL $(CMT_URL)/crypto/proof.proto > $(CMT_CRYPTO_TYPES)/proof.proto + @curl -sSL $(CMT_URL)/crypto/keys.proto > $(CMT_CRYPTO_TYPES)/keys.proto @mkdir -p $(CMT_LIBS) - @curl -fsSL $(CMT_URL)/libs/bits/$(CMT_PROTO)/types.proto > $(CMT_LIBS)/types.proto + @curl -sSL $(CMT_URL)/libs/bits/types.proto > $(CMT_LIBS)/types.proto @mkdir -p $(CMT_P2P) - @curl -fsSL $(CMT_URL)/p2p/$(CMT_PROTO)/conn.proto > $(CMT_P2P)/conn.proto - @curl -fsSL $(CMT_URL)/p2p/$(CMT_PROTO)/pex.proto > $(CMT_P2P)/pex.proto - @curl -fsSL $(CMT_URL)/p2p/$(CMT_PROTO)/types.proto > $(CMT_P2P)/types.proto + @curl -sSL $(CMT_URL)/p2p/types.proto > $(CMT_P2P)/types.proto -proto-update-deps: - @echo "Updating Protobuf dependencies: running 'buf dep update'" - $(DOCKER) run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName) buf dep update + $(DOCKER) run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName) buf mod update -.PHONY: proto-all proto-gen proto-swagger-gen proto-format proto-lint proto-check-breaking proto-update-deps proto-update-comet +.PHONY: proto-all proto-gen proto-swagger-gen proto-format proto-lint proto-check-breaking proto-update-deps ############################################################################### ### Localnet ### From 47c029ae333cba3ddbe5aaa4066056f6570847e5 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:14:09 -0700 Subject: [PATCH 14/87] remove leafOffsetRemappings and add a comment to offsetCache --- iavl/compactor.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/iavl/compactor.go b/iavl/compactor.go index 4c715f7b662a..a9c70ee64a73 100644 --- a/iavl/compactor.go +++ b/iavl/compactor.go @@ -30,9 +30,10 @@ type Compactor struct { versionsWriter *StructWriter[VersionInfo] kvlogWriter *KVLogWriter - leafOffsetRemappings map[uint32]uint32 - keyCache map[string]uint32 - offsetCache map[NodeID]uint32 + keyCache map[string]uint32 + // offsetCache holds the updated 1-based offsets of nodes affected by compacting. + // these are then used to update BranchLayout's left and right offsets. + offsetCache map[NodeID]uint32 // Running totals across all processed changesets leafOrphanCount uint32 From d4d932e03562496c2cf6a59302dc74fdae0c40a5 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:53:15 -0700 Subject: [PATCH 15/87] branch persisted removals and method removals --- iavl/branch_persisted.go | 38 +++++++++++++++--------- iavl/changeset.go | 62 ++-------------------------------------- 2 files changed, 28 insertions(+), 72 deletions(-) diff --git a/iavl/branch_persisted.go b/iavl/branch_persisted.go index 3a523decb6f9..8aa29bac6324 100644 --- a/iavl/branch_persisted.go +++ b/iavl/branch_persisted.go @@ -1,12 +1,31 @@ package iavlx -import "bytes" +import ( + "bytes" + "sync/atomic" +) type BranchPersisted struct { - store *Changeset - selfIdx uint32 - layout BranchLayout - leftPtr, rightPtr *NodePointer + store *Changeset + layout BranchLayout +} + +func (node *BranchPersisted) Left() *NodePointer { + return &NodePointer{ + mem: atomic.Pointer[MemNode]{}, + store: node.store, + fileIdx: node.layout.LeftOffset, + id: node.layout.Left, + } +} + +func (node *BranchPersisted) Right() *NodePointer { + return &NodePointer{ + mem: atomic.Pointer[MemNode]{}, + store: node.store, + fileIdx: node.layout.RightOffset, + id: node.layout.Right, + } } func (node *BranchPersisted) ID() NodeID { @@ -37,14 +56,6 @@ func (node *BranchPersisted) Value() ([]byte, error) { return nil, nil } -func (node *BranchPersisted) Left() *NodePointer { - return node.leftPtr -} - -func (node *BranchPersisted) Right() *NodePointer { - return node.rightPtr -} - func (node *BranchPersisted) Hash() []byte { return node.layout.Hash[:] } @@ -58,6 +69,7 @@ func (node *BranchPersisted) MutateBranch(version uint32) (*MemNode, error) { if err != nil { return nil, err } + memNode := &MemNode{ height: node.Height(), size: node.Size(), diff --git a/iavl/changeset.go b/iavl/changeset.go index b47ee10b16ba..9f98559329dc 100644 --- a/iavl/changeset.go +++ b/iavl/changeset.go @@ -170,56 +170,6 @@ func (cr *Changeset) resolveBranchWithIdx(nodeId NodeID, fileIdx uint32) (Branch } } -func (cr *Changeset) resolveNodeID(id NodeID) *NodePointer { - return &NodePointer{ - id: id, - store: cr.treeStore.getChangesetForVersion(uint32(id.Version())), - } -} - -// TODO(technicallyty): remove this? -func (cr *Changeset) resolveNodeRef(nodeRef NodeRef, selfIdx uint32) *NodePointer { - if nodeRef.IsNodeID() { - id := nodeRef.AsNodeID() - return &NodePointer{ - id: id, - store: cr.treeStore.getChangesetForVersion(uint32(id.Version())), - } - } - relPtr := nodeRef.AsRelativePointer() - offset := relPtr.Offset() - if nodeRef.IsLeaf() { - if offset < 1 { - panic(fmt.Sprintf("invalid leaf offset: %d", offset)) - } - itemIdx := uint32(offset - 1) - if itemIdx >= uint32(cr.leavesData.Count()) { - panic(fmt.Sprintf("leaf offset %d out of bounds (have %d leaves)", offset, cr.leavesData.Count())) - } - layout := cr.leavesData.UnsafeItem(itemIdx) - return &NodePointer{ - id: layout.Id, - store: cr, - fileIdx: uint32(offset), - } - } else { - idx := int64(selfIdx) + offset - if idx < 1 { - panic(fmt.Sprintf("invalid branch index: %d (selfIdx=%d, offset=%d)", idx, selfIdx, offset)) - } - itemIdx := uint32(idx - 1) - if itemIdx >= uint32(cr.branchesData.Count()) { - panic(fmt.Sprintf("branch index %d out of bounds (have %d branches)", idx, cr.branchesData.Count())) - } - layout := cr.branchesData.UnsafeItem(itemIdx) - return &NodePointer{ - id: layout.Id, - store: cr, - fileIdx: uint32(idx), - } - } -} - func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { if cr.evicted.Load() { return cr.treeStore.Resolve(nodeId, fileIdx) @@ -234,20 +184,14 @@ func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { } return &LeafPersisted{layout: layout, store: cr}, nil } else { - layout, actualIdx, err := cr.resolveBranchWithIdx(nodeId, fileIdx) + layout, _, err := cr.resolveBranchWithIdx(nodeId, fileIdx) if err != nil { return nil, err } - leftPtr := cr.resolveNodeID(layout.Left) - rightPtr := cr.resolveNodeID(layout.Right) - return &BranchPersisted{ - layout: layout, - store: cr, - selfIdx: actualIdx, - leftPtr: leftPtr, - rightPtr: rightPtr, + layout: layout, + store: cr, }, nil } } From e85a1aaad6a2d977798ce11a70318d00a6892cac Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:55:23 -0700 Subject: [PATCH 16/87] fully remove leafOffsetRemapping code --- iavl/compactor.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/iavl/compactor.go b/iavl/compactor.go index a9c70ee64a73..5ec0c6860414 100644 --- a/iavl/compactor.go +++ b/iavl/compactor.go @@ -73,19 +73,18 @@ func NewCompacter(logger *slog.Logger, reader *Changeset, opts CompactOptions, s } c := &Compactor{ - logger: logger, - criteria: opts.RetainCriteria, - compactWAL: opts.CompactWAL, - treeStore: store, - files: newFiles, - originalKvLogPath: reader.files.KVLogPath(), - kvlogWriter: kvlogWriter, - leavesWriter: NewStructWriter[LeafLayout](newFiles.leavesFile), - branchesWriter: NewStructWriter[BranchLayout](newFiles.branchesFile), - versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile), - keyCache: make(map[string]uint32), - leafOffsetRemappings: make(map[uint32]uint32), - offsetCache: make(map[NodeID]uint32), + logger: logger, + criteria: opts.RetainCriteria, + compactWAL: opts.CompactWAL, + treeStore: store, + files: newFiles, + originalKvLogPath: reader.files.KVLogPath(), + kvlogWriter: kvlogWriter, + leavesWriter: NewStructWriter[LeafLayout](newFiles.leavesFile), + branchesWriter: NewStructWriter[BranchLayout](newFiles.branchesFile), + versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile), + keyCache: make(map[string]uint32), + offsetCache: make(map[NodeID]uint32), } // Process first changeset immediately @@ -164,8 +163,6 @@ func (c *Compactor) processChangeset(reader *Changeset) error { return fmt.Errorf("failed to append leaf %s: %w", id, err) } - oldLeafFileIdx := leafStartOffset + j - c.leafOffsetRemappings[oldLeafFileIdx] = uint32(c.leavesWriter.Count()) c.offsetCache[id] = uint32(c.leavesWriter.Count()) } From 245982da7093b2bd185d25ca15d8a9d9919bc9d2 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 21 Oct 2025 18:30:18 -0400 Subject: [PATCH 17/87] fix bug --- iavl/commit_tree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index 21a879d1bc8e..4073bff075ba 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -79,7 +79,7 @@ func (c *CommitTree) workingHash() []byte { } root := c.root.mem.Load() - if root != nil { + if root != nil && root.hash != nil { // already computed working hash return root.hash } From 8585d2bbd5137695f35b5c80f3c40ac698f36b00 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:45:25 -0700 Subject: [PATCH 18/87] fix --- iavl/branch_layout.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iavl/branch_layout.go b/iavl/branch_layout.go index 0c9478e810a5..fdc45815dece 100644 --- a/iavl/branch_layout.go +++ b/iavl/branch_layout.go @@ -12,7 +12,7 @@ func init() { } const ( - SizeBranch = 88 + SizeBranch = 80 ) type BranchLayout struct { From eb32c2d3053e0f4e036afac6e44e431603977781 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 21 Oct 2025 18:47:02 -0400 Subject: [PATCH 19/87] lint fix --- iavl/branch_persisted.go | 2 +- iavl/changeset_writer.go | 4 ++-- iavl/commit_multi_tree.go | 8 ++++---- iavl/commit_tree.go | 15 ++++++++------- iavl/dot_graph.go | 4 ++-- iavl/kvlog_writer.go | 9 ++++----- iavl/leaf_persisted.go | 2 +- iavl/mem_node.go | 1 - iavl/multi_tree.go | 6 +++--- iavl/node_hash.go | 4 +--- iavl/node_update.go | 6 +++--- iavl/tree.go | 7 ++++--- iavl/tree_store.go | 3 ++- iavl/tree_test.go | 38 ++++++++++++++++++++------------------ server/util.go | 3 +-- 15 files changed, 56 insertions(+), 56 deletions(-) diff --git a/iavl/branch_persisted.go b/iavl/branch_persisted.go index 3a523decb6f9..8ba934b66629 100644 --- a/iavl/branch_persisted.go +++ b/iavl/branch_persisted.go @@ -99,7 +99,7 @@ func (node *BranchPersisted) Get(key []byte) (value []byte, index int64, err err } func (node *BranchPersisted) String() string { - //TODO implement me + // TODO implement me panic("implement me") } diff --git a/iavl/changeset_writer.go b/iavl/changeset_writer.go index 689d258a7061..8dd3480da6a2 100644 --- a/iavl/changeset_writer.go +++ b/iavl/changeset_writer.go @@ -47,7 +47,7 @@ func (cs *ChangesetWriter) WriteWALCommit(version uint32) error { return cs.kvlog.WriteCommit(version) } -func (cs *ChangesetWriter) SaveRoot(root *NodePointer, version uint32, totalLeaves, totalBranches uint32) error { +func (cs *ChangesetWriter) SaveRoot(root *NodePointer, version, totalLeaves, totalBranches uint32) error { if version != cs.stagedVersion { return fmt.Errorf("version mismatch: expected %d, got %d", cs.stagedVersion, version) } @@ -217,7 +217,7 @@ func (cs *ChangesetWriter) writeLeaf(np *NodePointer, node *MemNode) error { func (cs *ChangesetWriter) createNodeRef(parentIdx int64, np *NodePointer) NodeRef { if np.store == cs.reader { if np.id.IsLeaf() { - //return NodeRef(np.id) + // return NodeRef(np.id) return NodeRef(NewNodeRelativePointer(true, int64(np.fileIdx))) } else { // for branch nodes the relative offset is the difference between the parent ID index and the branch ID index diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index 9b6fc8950be6..f04077c77158 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -8,11 +8,11 @@ import ( "path/filepath" "runtime" - "cosmossdk.io/log" "github.com/alitto/pond/v2" dbm "github.com/cosmos/cosmos-db" protoio "github.com/cosmos/gogoproto/io" + "cosmossdk.io/log" "cosmossdk.io/store/mem" "cosmossdk.io/store/metrics" pruningtypes "cosmossdk.io/store/pruning/types" @@ -151,7 +151,7 @@ func (db *CommitMultiTree) CacheWrap() storetypes.CacheWrap { } func (db *CommitMultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - //TODO implement tracking + // TODO implement tracking return db.CacheMultiStore() } @@ -344,7 +344,7 @@ func (db *CommitMultiTree) AddListeners(keys []storetypes.StoreKey) { } func (db *CommitMultiTree) PopStateCache() []*storetypes.StoreKVPair { - //TODO implement me + // TODO implement me panic("implement me") } @@ -353,7 +353,7 @@ func (db *CommitMultiTree) SetMetrics(metrics metrics.StoreMetrics) { } func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, error) { - //n := len(treeNames) + // n := len(treeNames) //trees := make([]*CommitTree, n) //treesByName := make(map[string]int, n) //for i, name := range treeNames { diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index 4073bff075ba..a733039fd6e2 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -7,7 +7,6 @@ import ( "sync/atomic" "cosmossdk.io/log" - pruningtypes "cosmossdk.io/store/pruning/types" storetypes "cosmossdk.io/store/types" ) @@ -321,8 +320,8 @@ func (c *CommitTree) Close() error { if c.walChan != nil { close(c.walChan) } - //close(c.commitChan) - //return <-c.commitDone + // close(c.commitChan) + // return <-c.commitDone return c.store.Close() } @@ -390,14 +389,16 @@ func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uin } if memNode.IsLeaf() { - return + return count } // Continue traversing to find nodes to evict count += evictTraverse(memNode.left, depth+1, evictionDepth, evictVersion) count += evictTraverse(memNode.right, depth+1, evictionDepth, evictVersion) - return + return count } -var _ storetypes.CommitKVStore = &CommitTree{} -var _ parentTree = &CommitTree{} +var ( + _ storetypes.CommitKVStore = &CommitTree{} + _ parentTree = &CommitTree{} +) diff --git a/iavl/dot_graph.go b/iavl/dot_graph.go index bd35b077d7e6..c5043b0c19d7 100644 --- a/iavl/dot_graph.go +++ b/iavl/dot_graph.go @@ -5,7 +5,7 @@ import ( "io" ) -func DebugTraverse(nodePtr *NodePointer, onNode func(node Node, parent Node, direction string) error) error { +func DebugTraverse(nodePtr *NodePointer, onNode func(node, parent Node, direction string) error) error { if nodePtr == nil { return nil } @@ -52,7 +52,7 @@ func RenderDotGraph(writer io.Writer, nodePtr *NodePointer) error { return finishGraph() } - err = DebugTraverse(nodePtr, func(node Node, parent Node, direction string) error { + err = DebugTraverse(nodePtr, func(node, parent Node, direction string) error { key, err := node.Key() if err != nil { return err diff --git a/iavl/kvlog_writer.go b/iavl/kvlog_writer.go index da1f6298e6ac..19bb3083634d 100644 --- a/iavl/kvlog_writer.go +++ b/iavl/kvlog_writer.go @@ -16,13 +16,12 @@ func NewKVDataWriter(file *os.File) *KVLogWriter { return &KVLogWriter{ FileWriter: fw, } - } func (kvs *KVLogWriter) WriteK(key []byte) (offset uint32, err error) { _, err = kvs.Write([]byte{KVLogEntryTypeExtraK}) if err != nil { - return + return offset, err } return kvs.writeLenPrefixedBytes(key) @@ -31,7 +30,7 @@ func (kvs *KVLogWriter) WriteK(key []byte) (offset uint32, err error) { func (kvs *KVLogWriter) WriteKV(key, value []byte) (offset uint32, err error) { _, err = kvs.Write([]byte{KVLogEntryTypeExtraKV}) if err != nil { - return + return offset, err } offset, err = kvs.writeLenPrefixedBytes(key) @@ -95,13 +94,13 @@ func (kvs *KVLogWriter) writeLenPrefixedBytes(key []byte) (offset uint32, err er // write little endian uint32 length prefix err = kvs.writeLEU32(uint32(lenKey)) if err != nil { - return + return offset, err } // write key bytes _, err = kvs.Write(key) if err != nil { - return + return offset, err } return offset, nil diff --git a/iavl/leaf_persisted.go b/iavl/leaf_persisted.go index 095678737882..895cd952b844 100644 --- a/iavl/leaf_persisted.go +++ b/iavl/leaf_persisted.go @@ -81,7 +81,7 @@ func (node *LeafPersisted) Get(key []byte) (value []byte, index int64, err error } func (node *LeafPersisted) String() string { - //TODO implement me + // TODO implement me panic("implement me") } diff --git a/iavl/mem_node.go b/iavl/mem_node.go index a9582625e7aa..a8bb4a0168db 100644 --- a/iavl/mem_node.go +++ b/iavl/mem_node.go @@ -109,7 +109,6 @@ func (node *MemNode) String() string { if node.IsLeaf() { return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, value:%x}", node.key, node.version, node.size, node.value) } else { - return fmt.Sprintf("MemNode{key:%x, version:%d, size:%d, height:%d, left:%s, right:%s}", node.key, node.version, node.size, node.height, node.left, node.right) } } diff --git a/iavl/multi_tree.go b/iavl/multi_tree.go index 44ee223ac4ee..dfe4858a6785 100644 --- a/iavl/multi_tree.go +++ b/iavl/multi_tree.go @@ -28,7 +28,7 @@ func (t *MultiTree) CacheWrap() storetypes.CacheWrap { } func (t *MultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - //TODO implement tracing + // TODO implement tracing return t.CacheWrap() } @@ -67,12 +67,12 @@ func (t *MultiTree) TracingEnabled() bool { } func (t *MultiTree) SetTracer(w io.Writer) storetypes.MultiStore { - //TODO implement me + // TODO implement me panic("implement me") } func (t *MultiTree) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { - //TODO implement me + // TODO implement me panic("implement me") } diff --git a/iavl/node_hash.go b/iavl/node_hash.go index 0ce5f12317e1..5ec8c967910a 100644 --- a/iavl/node_hash.go +++ b/iavl/node_hash.go @@ -25,9 +25,7 @@ func computeHash(node Node, leftHash, rightHash []byte) ([]byte, error) { return hasher.Sum(nil), nil } -var ( - emptyHash = sha256.New().Sum(nil) -) +var emptyHash = sha256.New().Sum(nil) // Writes the node's hash to the given `io.Writer`. This function recursively calls // children to update hashes. diff --git a/iavl/node_update.go b/iavl/node_update.go index db8d7837c7ef..dd2000860bd3 100644 --- a/iavl/node_update.go +++ b/iavl/node_update.go @@ -93,7 +93,7 @@ func setRecursive(nodePtr *NodePointer, leafNode *MemNode, ctx *MutationContext) type newKeyWrapper struct { key []byte - //keyRef keyRefLink + // keyRef keyRefLink } // removeRecursive returns: @@ -138,7 +138,7 @@ func removeRecursive(nodePtr *NodePointer, key []byte, ctx *MutationContext) (va ctx.AddOrphan(nodePtr.id) return value, node.Right(), &newKeyWrapper{ key: nodeKey, - //keyRef: nodePtr, + // keyRef: nodePtr, }, nil } @@ -181,7 +181,7 @@ func removeRecursive(nodePtr *NodePointer, key []byte, ctx *MutationContext) (va newNode.right = newRight if newKey != nil { newNode.key = newKey.key - //newNode._keyRef = newKey.keyRef + // newNode._keyRef = newKey.keyRef } err = newNode.updateHeightSize() diff --git a/iavl/tree.go b/iavl/tree.go index 46e7779bda76..8dc3c0f6118e 100644 --- a/iavl/tree.go +++ b/iavl/tree.go @@ -4,7 +4,6 @@ import ( io "io" corestore "cosmossdk.io/core/store" - storetypes "cosmossdk.io/store/types" ) @@ -139,5 +138,7 @@ func (tree *Tree) ReverseIterator(start, end []byte) corestore.Iterator { return NewIterator(start, end, false, tree.root, tree.zeroCopy) } -var _ storetypes.CacheKVStore = (*Tree)(nil) -var _ parentTree = (*Tree)(nil) +var ( + _ storetypes.CacheKVStore = (*Tree)(nil) + _ parentTree = (*Tree)(nil) +) diff --git a/iavl/tree_store.go b/iavl/tree_store.go index 0e33f5cb46ed..b2c1dcda19fa 100644 --- a/iavl/tree_store.go +++ b/iavl/tree_store.go @@ -6,8 +6,9 @@ import ( "sync" "sync/atomic" - "cosmossdk.io/log" "github.com/tidwall/btree" + + "cosmossdk.io/log" ) type TreeStore struct { diff --git a/iavl/tree_test.go b/iavl/tree_test.go index 6686c4d7a671..0bb57e2df022 100644 --- a/iavl/tree_test.go +++ b/iavl/tree_test.go @@ -7,13 +7,14 @@ import ( "runtime/debug" "testing" - corestore "cosmossdk.io/core/store" - sdklog "cosmossdk.io/log" "github.com/cosmos/iavl" dbm "github.com/cosmos/iavl/db" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" "pgregory.net/rapid" + + corestore "cosmossdk.io/core/store" + sdklog "cosmossdk.io/log" ) func TestBasicTest(t *testing.T) { @@ -31,7 +32,7 @@ func TestBasicTest(t *testing.T) { require.Equal(t, []byte{1}, val) tree.Set([]byte{1}, []byte{2}) - //renderTree(t, tree) + // renderTree(t, tree) val = tree.Get([]byte{0}) require.NoError(t, err) @@ -41,7 +42,7 @@ func TestBasicTest(t *testing.T) { require.Equal(t, []byte{2}, val) tree.Set([]byte{2}, []byte{3}) - //renderTree(t, tree) + // renderTree(t, tree) val = tree.Get([]byte{0}) require.NoError(t, err) @@ -58,7 +59,7 @@ func TestBasicTest(t *testing.T) { require.Nil(t, val) tree.Delete([]byte{1}) - //renderTree(t, tree) + // renderTree(t, tree) val = tree.Get([]byte{1}) require.NoError(t, err) @@ -75,7 +76,8 @@ func TestBasicTest(t *testing.T) { func renderTree(t interface { require.TestingT Logf(format string, args ...any) -}, tree *Tree) { +}, tree *Tree, +) { graph := &bytes.Buffer{} require.NoError(t, RenderDotGraph(graph, tree.root)) t.Logf("tree graph:\n%s", graph.String()) @@ -95,7 +97,7 @@ func testIAVLXSims(t *rapid.T) { t.Fatalf("panic recovered: %v\nStack trace:\n%s", r, debug.Stack()) } }() - //logger := sdklog.NewTestLogger(t) + // logger := sdklog.NewTestLogger(t) logger := sdklog.NewNopLogger() dbV1 := dbm.NewMemDB() treeV1 := iavl.NewMutableTree(dbV1, 500000, true, logger) @@ -180,7 +182,7 @@ func (s *SimMachine) set(t *rapid.T) { branch := s.treeV2.CacheWrap().(*Tree) branch.Set(key, value) branch.Write() - //require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees") + // require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees") if updated { require.NotNil(t, s.existingKeys[string(key)], "key shouldn't have been marked as updated") } else { @@ -193,7 +195,7 @@ func (s *SimMachine) set(t *rapid.T) { } func (s *SimMachine) get(t *rapid.T) { - var key = s.selectKey(t) + key := s.selectKey(t) valueV1, errV1 := s.treeV1.Get(key) require.NoError(t, errV1, "failed to get key from V1 tree") valueV2 := s.treeV2.CacheWrap().(*Tree).Get(key) @@ -225,7 +227,7 @@ func (s *SimMachine) delete(t *rapid.T) { branch := s.treeV2.CacheWrap().(*Tree) branch.Delete(key) branch.Write() - //require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees") + // require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees") // TODO v1 & v2 have slightly different behaviors for the value returned on removal. We should re-enable this and check. //if valueV1 == nil || len(valueV1) == 0 { // require.Empty(t, valueV2, "value should be empty for removed key in V2 tree") @@ -248,9 +250,9 @@ func (s *SimMachine) Iterate(t *rapid.T) { // TODO add cases where we nudge start or end up or down a little - //ascending := rapid.Bool().Draw(t, "ascending") + // ascending := rapid.Bool().Draw(t, "ascending") - //s.compareIterators(t, start, end, ascending) + // s.compareIterators(t, start, end, ascending) } func (s *SimMachine) Commit(t *rapid.T) { @@ -258,9 +260,9 @@ func (s *SimMachine) Commit(t *rapid.T) { require.NoError(t, err, "failed to save version in V1 tree") commitId2 := s.treeV2.Commit() require.NoError(t, err, "failed to save version in V2 tree") - //s.debugDump(t) + // s.debugDump(t) err = VerifyTree(s.treeV2) - //if err != nil { + // if err != nil { // branches := s.treeV2.rollingDiff.branchData // n := branches.Count() // buf := &bytes.Buffer{} @@ -277,7 +279,7 @@ func (s *SimMachine) Commit(t *rapid.T) { //} require.NoError(t, err, "failed to verify V2 tree") require.Equal(t, hash1, commitId2.Hash, "hash mismatch between V1 and V2 trees") - //require.Equal(t, v1, v2, "version mismatch between V1 and V2 trees") + // require.Equal(t, v1, v2, "version mismatch between V1 and V2 trees") } func (s *SimMachine) debugDump(t *rapid.T) { @@ -286,7 +288,7 @@ func (s *SimMachine) debugDump(t *rapid.T) { graph1 := &bytes.Buffer{} iavl.WriteDOTGraph(graph1, s.treeV1.ImmutableTree, nil) t.Logf("V1 tree:\n%s", graph1.String()) - //renderTree(t, s.treeV2.Branch()) + // renderTree(t, s.treeV2.Branch()) iter2 := s.treeV2.CacheWrap().(*Tree).Iterator(nil, nil) s.debugDumpTree(t, iter2) } @@ -305,7 +307,7 @@ func (s *SimMachine) debugDumpTree(t *rapid.T, iter corestore.Iterator) { t.Log(dumpStr) } -//func (s *SimMachine) CheckoutVersion(t *rapid.T) { +// func (s *SimMachine) CheckoutVersion(t *rapid.T) { // if s.treeV1.Version() <= 1 { // // cannot checkout version 1 or lower // return @@ -332,7 +334,7 @@ func (s *SimMachine) compareIterators(t *rapid.T, start, end []byte, ascending b compareIteratorsAtVersion(t, iter1, iter2) } -func compareIteratorsAtVersion(t *rapid.T, iterV1 corestore.Iterator, iterV2 corestore.Iterator) { +func compareIteratorsAtVersion(t *rapid.T, iterV1, iterV2 corestore.Iterator) { defer func() { require.NoError(t, iterV1.Close(), "failed to close iterator for V1 tree") }() diff --git a/server/util.go b/server/util.go index 37b13f6fb43f..a211e0981c5c 100644 --- a/server/util.go +++ b/server/util.go @@ -25,15 +25,14 @@ import ( "golang.org/x/sync/errgroup" "cosmossdk.io/log" - "cosmossdk.io/store" "cosmossdk.io/store/snapshots" snapshottypes "cosmossdk.io/store/snapshots/types" storetypes "cosmossdk.io/store/types" - iavlx "github.com/cosmos/cosmos-sdk/iavl" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client/flags" + iavlx "github.com/cosmos/cosmos-sdk/iavl" "github.com/cosmos/cosmos-sdk/server/config" "github.com/cosmos/cosmos-sdk/server/types" sdk "github.com/cosmos/cosmos-sdk/types" From 09e0de93bda657b9f1e2c011e3700a9781d697b9 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:53:53 -0700 Subject: [PATCH 20/87] fix resolve --- iavl/changeset.go | 52 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/iavl/changeset.go b/iavl/changeset.go index 9f98559329dc..b257e714f3b3 100644 --- a/iavl/changeset.go +++ b/iavl/changeset.go @@ -177,22 +177,52 @@ func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { cr.Pin() defer cr.Unpin() - if nodeId.IsLeaf() { - layout, err := cr.ResolveLeaf(nodeId, fileIdx) + if fileIdx == 0 { + cs := cr.treeStore.getChangesetForVersion(uint32(nodeId.Version())) + cs.Pin() + defer cs.Unpin() + version := uint32(nodeId.Version()) + vi, err := cs.getVersionInfo(version) if err != nil { return nil, err } - return &LeafPersisted{layout: layout, store: cr}, nil + if nodeId.IsLeaf() { + leaf, err := cs.leavesData.FindByID(nodeId, &vi.Leaves) + if err != nil { + return nil, err + } + return &LeafPersisted{ + store: cs, + selfIdx: 0, + layout: *leaf, + }, nil + } else { + branch, err := cs.branchesData.FindByID(nodeId, &vi.Branches) + if err != nil { + return nil, err + } + return &BranchPersisted{ + store: cs, + layout: *branch, + }, nil + } } else { - layout, _, err := cr.resolveBranchWithIdx(nodeId, fileIdx) - if err != nil { - return nil, err + if nodeId.IsLeaf() { + layout, err := cr.ResolveLeaf(nodeId, fileIdx) + if err != nil { + return nil, err + } + return &LeafPersisted{layout: layout, store: cr}, nil + } else { + layout, _, err := cr.resolveBranchWithIdx(nodeId, fileIdx) + if err != nil { + return nil, err + } + return &BranchPersisted{ + layout: layout, + store: cr, + }, nil } - - return &BranchPersisted{ - layout: layout, - store: cr, - }, nil } } From 93aa22c8f70f2b304efd5bfbbf0133268a332bb9 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:56:58 -0700 Subject: [PATCH 21/87] some comments --- iavl/changeset.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/iavl/changeset.go b/iavl/changeset.go index b257e714f3b3..c5713333d72e 100644 --- a/iavl/changeset.go +++ b/iavl/changeset.go @@ -177,10 +177,14 @@ func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { cr.Pin() defer cr.Unpin() + // we don't have a fileIdx, so its probably not in this changeset. if fileIdx == 0 { + // load up the changeset for this node cs := cr.treeStore.getChangesetForVersion(uint32(nodeId.Version())) cs.Pin() defer cs.Unpin() + + // get version data version := uint32(nodeId.Version()) vi, err := cs.getVersionInfo(version) if err != nil { @@ -207,19 +211,17 @@ func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { }, nil } } else { + // since we have the fileIdx, we know it's in this changeset. + // we can just directly index in this changeset's leaf/branch data. if nodeId.IsLeaf() { - layout, err := cr.ResolveLeaf(nodeId, fileIdx) - if err != nil { - return nil, err - } - return &LeafPersisted{layout: layout, store: cr}, nil + itemIdx := fileIdx - 1 + leafLayout := *cr.leavesData.UnsafeItem(itemIdx) + return &LeafPersisted{layout: leafLayout, store: cr}, nil } else { - layout, _, err := cr.resolveBranchWithIdx(nodeId, fileIdx) - if err != nil { - return nil, err - } + itemIdx := fileIdx - 1 + branchLayout := *cr.branchesData.UnsafeItem(itemIdx) return &BranchPersisted{ - layout: layout, + layout: branchLayout, store: cr, }, nil } From 98aec1c9036d28a9ccdf2f5bc28fe13b7aca011e Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 23 Oct 2025 13:25:25 -0400 Subject: [PATCH 22/87] add iavlx-options to server --- server/start.go | 1 + server/util.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/server/start.go b/server/start.go index aab620b27548..4c92e70e7f1a 100644 --- a/server/start.go +++ b/server/start.go @@ -76,6 +76,7 @@ const ( FlagIAVLCacheSize = "iavl-cache-size" FlagDisableIAVLFastNode = "iavl-disable-fastnode" FlagIAVLSyncPruning = "iavl-sync-pruning" + FlagIAVLXOptions = "iavlx-options" FlagShutdownGrace = "shutdown-grace" // state sync-related flags diff --git a/server/util.go b/server/util.go index a211e0981c5c..4e13957e19c8 100644 --- a/server/util.go +++ b/server/util.go @@ -2,6 +2,7 @@ package server import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -584,9 +585,18 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) { baseapp.SetChainID(chainID), baseapp.SetQueryGasLimit(cast.ToUint64(appOpts.Get(FlagQueryGasLimit))), func(bapp *baseapp.BaseApp) { + opts := &iavlx.Options{} + optsJson, ok := appOpts.Get(FlagIAVLXOptions).(string) + if ok && optsJson != "" { + err := json.Unmarshal([]byte(optsJson), opts) + if err != nil { + panic(fmt.Errorf("failed to unmarshal iavlx options: %w", err)) + } + } + db, err := iavlx.LoadDB( filepath.Join(homeDir, "data", "iavlx"), - &iavlx.Options{}, + opts, bapp.Logger(), ) if err != nil { From 179d2f31433e76aa96520470e8ff8d1b0de0ba6a Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 24 Oct 2025 15:42:59 -0400 Subject: [PATCH 23/87] fix data race --- iavl/commit_tree.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index a733039fd6e2..b3c7151238ee 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "cosmossdk.io/log" + pruningtypes "cosmossdk.io/store/pruning/types" storetypes "cosmossdk.io/store/types" ) @@ -20,7 +21,7 @@ type CommitTree struct { zeroCopy bool evictionDepth uint8 - evictorRunning bool + evictorRunning atomic.Bool lastEvictVersion uint32 writeWal bool @@ -274,7 +275,7 @@ func (c *CommitTree) reinitWalProc() { } func (c *CommitTree) startEvict(evictVersion uint32) { - if c.evictorRunning { + if c.evictorRunning.Load() { // eviction in progress return } @@ -291,12 +292,12 @@ func (c *CommitTree) startEvict(evictVersion uint32) { } c.logger.Debug("start eviction", "version", evictVersion, "depth", c.evictionDepth) - c.evictorRunning = true + c.evictorRunning.Store(true) go func() { evictedCount := evictTraverse(latest, 0, c.evictionDepth, evictVersion) c.logger.Debug("eviction completed", "version", evictVersion, "lastEvict", c.lastEvictVersion, "evictedNodes", evictedCount) c.lastEvictVersion = evictVersion - c.evictorRunning = false + c.evictorRunning.Store(false) }() } From b255d89965e5dc0bd4b8484211209b5ac6e39e0f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 24 Oct 2025 17:47:33 -0400 Subject: [PATCH 24/87] perf: benchmarking with sim tests --- server/util.go | 2 ++ simapp/app_di.go | 23 +++++++++++++++++++++++ simapp/sim_test.go | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/server/util.go b/server/util.go index 4e13957e19c8..f5b9a3effa60 100644 --- a/server/util.go +++ b/server/util.go @@ -26,6 +26,7 @@ import ( "golang.org/x/sync/errgroup" "cosmossdk.io/log" + "cosmossdk.io/store" "cosmossdk.io/store/snapshots" snapshottypes "cosmossdk.io/store/snapshots/types" @@ -585,6 +586,7 @@ func DefaultBaseappOptions(appOpts types.AppOptions) []func(*baseapp.BaseApp) { baseapp.SetChainID(chainID), baseapp.SetQueryGasLimit(cast.ToUint64(appOpts.Get(FlagQueryGasLimit))), func(bapp *baseapp.BaseApp) { + fmt.Println("Loading IAVLX as the commit multi-store...") opts := &iavlx.Options{} optsJson, ok := appOpts.Get(FlagIAVLXOptions).(string) if ok && optsJson != "" { diff --git a/simapp/app_di.go b/simapp/app_di.go index 3e8e010d0acb..b3213107a6a6 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -3,7 +3,11 @@ package simapp import ( + "encoding/json" + "fmt" "io" + "os" + "path/filepath" dbm "github.com/cosmos/cosmos-db" @@ -14,8 +18,10 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + iavlx "github.com/cosmos/cosmos-sdk/iavl" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/api" @@ -100,6 +106,23 @@ func NewSimApp( appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { + iavlxOpts, useIavlx := os.LookupEnv("IAVLX") + if useIavlx && iavlxOpts != "" { + fmt.Println("Setting up IAVLX as the underlying commit multi-store") + var opts iavlx.Options + err := json.Unmarshal([]byte(iavlxOpts), &opts) + if err != nil { + panic(err) + } + baseAppOptions = append(baseAppOptions, func(bApp *baseapp.BaseApp) { + dir := filepath.Join(appOpts.Get(flags.FlagHome).(string), "data", "iavlx") + db, err := iavlx.LoadDB(dir, &opts, logger) + if err != nil { + panic(err) + } + bApp.SetCMS(db) + }) + } var ( app = &SimApp{} appBuilder *runtime.AppBuilder diff --git a/simapp/sim_test.go b/simapp/sim_test.go index c76a12735d23..55ead7b3ce7f 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -157,7 +157,7 @@ func IsEmptyValidatorSetErr(err error) bool { } func TestAppStateDeterminism(t *testing.T) { - const numTimesToRunPerSeed = 3 + const numTimesToRunPerSeed = 1 var seeds []int64 if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue { // We will be overriding the random seed and just run a single simulation on the provided seed value From 357eb54d11a2c7a88ad183f8ad581bcd28192178 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 24 Oct 2025 18:33:25 -0400 Subject: [PATCH 25/87] add fsync interval option --- iavl/options.go | 3 +++ iavl/tree_store.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/iavl/options.go b/iavl/options.go index ee780336957f..db722bedcf18 100644 --- a/iavl/options.go +++ b/iavl/options.go @@ -37,6 +37,9 @@ type Options struct { // Setting to 0 means create reader every version (high mmap churn) // Higher values reduce mmap overhead but delay when data becomes readable ReaderUpdateInterval uint32 `json:"reader_update_interval"` + + // FsyncInterval defines how often to fsync WAL when using async mode (in versions). + FsyncInterval uint32 `json:"fsync_interval"` } // GetWalSyncBufferSize returns the actual buffer size to use (handling 0 = 1 case) diff --git a/iavl/tree_store.go b/iavl/tree_store.go index b2c1dcda19fa..9471d551a8c1 100644 --- a/iavl/tree_store.go +++ b/iavl/tree_store.go @@ -210,7 +210,7 @@ func (ts *TreeStore) SaveRoot(version uint32, root *NodePointer, totalLeaves, to default: } files := ts.currentWriter.files - if files.needsSync.CompareAndSwap(false, true) { + if version%ts.opts.FsyncInterval == 0 && files.needsSync.CompareAndSwap(false, true) { ts.syncQueue <- files } } else { From 6918a0dea46d7178de7e536cc7457c2c6b1ffb37 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 12:13:07 -0400 Subject: [PATCH 26/87] perf: optimize getConfigKey --- types/config.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/types/config.go b/types/config.go index 1c61968bb06b..e4ef476e8e31 100644 --- a/types/config.go +++ b/types/config.go @@ -31,6 +31,7 @@ type Config struct { var ( configRegistry = make(map[string]*Config) registryMutex sync.Mutex + configKey string ) // getConfigKey returns a unique config scope identifier. @@ -40,6 +41,10 @@ func getConfigKey() string { return id } + if configKey != "" { + return configKey + } + exe, errExec := os.Executable() host, errHost := os.Hostname() pid := os.Getpid() @@ -51,7 +56,8 @@ func getConfigKey() string { host = "unknown-host" } - return fmt.Sprintf("%s|%s|%d", host, exe, pid) + configKey = fmt.Sprintf("%s|%s|%d", host, exe, pid) + return configKey } // NewConfig returns a new Config with default values. From 2a4b6782f120511ed49a05f61af20e99a11982cc Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 12:47:40 -0400 Subject: [PATCH 27/87] perf: eliminate blocking queues --- iavl/changeset_files.go | 4 +-- iavl/changeset_writer.go | 13 ++++++++- iavl/commit_tree.go | 35 +++++++++++++---------- iavl/non_blocking_queue.go | 57 ++++++++++++++++++++++++++++++++++++++ iavl/options.go | 8 +++--- iavl/tree_store.go | 37 +++++++++++++++++-------- 6 files changed, 120 insertions(+), 34 deletions(-) create mode 100644 iavl/non_blocking_queue.go diff --git a/iavl/changeset_files.go b/iavl/changeset_files.go index 79f8e66aae96..7f79dfabb410 100644 --- a/iavl/changeset_files.go +++ b/iavl/changeset_files.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "path/filepath" - "sync/atomic" ) type ChangesetFiles struct { @@ -23,8 +22,7 @@ type ChangesetFiles struct { info *ChangesetInfo infoMmap *StructMmap[ChangesetInfo] - closed bool - needsSync atomic.Bool + closed bool } func OpenChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogPath string) (*ChangesetFiles, error) { diff --git a/iavl/changeset_writer.go b/iavl/changeset_writer.go index 8dd3480da6a2..322338353a55 100644 --- a/iavl/changeset_writer.go +++ b/iavl/changeset_writer.go @@ -3,12 +3,14 @@ package iavlx import ( "errors" "fmt" + "sync/atomic" ) type ChangesetWriter struct { stagedVersion uint32 - files *ChangesetFiles + files *ChangesetFiles + needsSync atomic.Bool kvlog *KVLogWriter branchesData *StructWriter[BranchLayout] @@ -48,6 +50,8 @@ func (cs *ChangesetWriter) WriteWALCommit(version uint32) error { } func (cs *ChangesetWriter) SaveRoot(root *NodePointer, version, totalLeaves, totalBranches uint32) error { + cs.needsSync.Store(true) + if version != cs.stagedVersion { return fmt.Errorf("version mismatch: expected %d, got %d", cs.stagedVersion, version) } @@ -261,3 +265,10 @@ func (cs *ChangesetWriter) Seal() (*Changeset, error) { func (cs *ChangesetWriter) StartVersion() uint32 { return cs.files.StartVersion() } + +func (cs *ChangesetWriter) SyncWAL() error { + if !cs.needsSync.CompareAndSwap(true, false) { + return nil + } + return cs.files.kvlogFile.Sync() +} diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index b3c7151238ee..b17b53df2b01 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -25,7 +25,7 @@ type CommitTree struct { lastEvictVersion uint32 writeWal bool - walChan chan<- []KVUpdate + walQueue *NonBlockingQueue[[]KVUpdate] walDone <-chan error pendingOrphans [][]NodeID @@ -56,7 +56,7 @@ func (c *CommitTree) applyChangesToParent(origRoot, newRoot *NodePointer, update c.pendingOrphans = append(c.pendingOrphans, updateBatch.Orphans...) if c.writeWal { - c.walChan <- updateBatch.Updates + c.walQueue.Send(updateBatch.Updates) } return nil @@ -112,7 +112,7 @@ func (c *CommitTree) commit() (storetypes.CommitID, error) { defer c.writeMutex.Unlock() if c.writeWal { - close(c.walChan) + c.walQueue.Close() } // compute hash and assign node IDs @@ -257,19 +257,25 @@ func (c *CommitTree) reinitWalProc() { return } - walChan := make(chan []KVUpdate, 2048) + walQueue := NewNonBlockingQueue[[]KVUpdate]() walDone := make(chan error, 1) - c.walChan = walChan + c.walQueue = walQueue c.walDone = walDone go func() { - defer close(walDone) - for updates := range walChan { - err := c.store.WriteWALUpdates(updates) - if err != nil { - walDone <- err + for { + batch := walQueue.Receive() + if batch == nil { + close(walDone) return } + for _, updates := range batch { + err := c.store.WriteWALUpdates(updates) + if err != nil { + walDone <- err + return + } + } } }() } @@ -318,11 +324,10 @@ func (c *CommitTree) GetImmutable(version int64) (storetypes.CacheKVStore, error } func (c *CommitTree) Close() error { - if c.walChan != nil { - close(c.walChan) + if c.walQueue != nil { + c.walQueue.Close() + // TODO do we need to wait for WAL done?? } - // close(c.commitChan) - // return <-c.commitDone return c.store.Close() } @@ -378,6 +383,8 @@ func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []by } func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uint32) (count int) { + // TODO check height, and don't traverse if tree is too short + memNode := np.mem.Load() if memNode == nil { return 0 diff --git a/iavl/non_blocking_queue.go b/iavl/non_blocking_queue.go new file mode 100644 index 000000000000..aaf89c20a82a --- /dev/null +++ b/iavl/non_blocking_queue.go @@ -0,0 +1,57 @@ +package iavlx + +import "sync" + +type NonBlockingQueue[T any] struct { + mu sync.Mutex + cond *sync.Cond + queue []T + closed bool +} + +func NewNonBlockingQueue[T any]() *NonBlockingQueue[T] { + res := &NonBlockingQueue[T]{} + res.cond = sync.NewCond(&res.mu) + return res +} + +func (q *NonBlockingQueue[T]) Send(item T) { + q.mu.Lock() + defer q.mu.Unlock() + if q.closed { + panic("NonBlockingQueue is closed, can't send") + } + q.queue = append(q.queue, item) + q.cond.Signal() +} + +func (q *NonBlockingQueue[T]) Receive() []T { + q.mu.Lock() + defer q.mu.Unlock() + for len(q.queue) == 0 && !q.closed { + q.cond.Wait() + } + + res := q.queue + q.queue = nil + return res +} + +func (q *NonBlockingQueue[T]) MaybeReceive() (batch []T, closed bool) { + q.mu.Lock() + defer q.mu.Unlock() + if len(q.queue) == 0 && !q.closed { + return nil, false + } + + res := q.queue + q.queue = nil + return res, q.closed +} + +func (q *NonBlockingQueue[T]) Close() { + q.mu.Lock() + defer q.mu.Unlock() + q.closed = true + q.cond.Broadcast() +} diff --git a/iavl/options.go b/iavl/options.go index db722bedcf18..df43ac0820e9 100644 --- a/iavl/options.go +++ b/iavl/options.go @@ -1,9 +1,6 @@ package iavlx type Options struct { - // ZeroCopy attempts to reduce copying of buffers, but this isn't really implemented yet and may not even be safe to implement. - ZeroCopy bool `json:"zero_copy"` - // EvictDepth defines the depth at which eviction occurs. 255 means no eviction. EvictDepth uint8 `json:"evict_depth"` @@ -38,8 +35,11 @@ type Options struct { // Higher values reduce mmap overhead but delay when data becomes readable ReaderUpdateInterval uint32 `json:"reader_update_interval"` - // FsyncInterval defines how often to fsync WAL when using async mode (in versions). + // FsyncInterval defines how often to fsync WAL when using async mode (in millisconds). FsyncInterval uint32 `json:"fsync_interval"` + + // ZeroCopy attempts to reduce copying of buffers, but this isn't really implemented yet and may not even be safe to implement. + ZeroCopy bool `json:"zero_copy"` } // GetWalSyncBufferSize returns the actual buffer size to use (handling 0 = 1 case) diff --git a/iavl/tree_store.go b/iavl/tree_store.go index 9471d551a8c1..472b15cf5ef4 100644 --- a/iavl/tree_store.go +++ b/iavl/tree_store.go @@ -5,6 +5,7 @@ import ( "fmt" "sync" "sync/atomic" + "time" "github.com/tidwall/btree" @@ -24,7 +25,7 @@ type TreeStore struct { opts Options - syncQueue chan *ChangesetFiles + syncQueue *NonBlockingQueue[*ChangesetFiles] syncDone chan error cleanupProc *cleanupProc @@ -54,9 +55,8 @@ func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, e ts.cleanupProc = newCleanupProc(ts) - if options.WriteWAL && options.WalSyncBuffer >= 0 { - bufferSize := options.GetWalSyncBufferSize() - ts.syncQueue = make(chan *ChangesetFiles, bufferSize) + if options.WriteWAL && options.FsyncInterval > 0 { + ts.syncQueue = NewNonBlockingQueue[*ChangesetFiles]() ts.syncDone = make(chan error) go ts.syncProc() } @@ -209,10 +209,6 @@ func (ts *TreeStore) SaveRoot(version uint32, root *NodePointer, totalLeaves, to } default: } - files := ts.currentWriter.files - if version%ts.opts.FsyncInterval == 0 && files.needsSync.CompareAndSwap(false, true) { - ts.syncQueue <- files - } } else { // Otherwise, sync immediately err := ts.currentWriter.files.kvlogFile.Sync() @@ -249,6 +245,10 @@ func (ts *TreeStore) SaveRoot(version uint32, root *NodePointer, totalLeaves, to if err != nil { return fmt.Errorf("failed to seal changeset for version %d: %w", version, err) } + if ts.syncQueue != nil { + // if sync queue is enabled, queue the files for fsync + ts.syncQueue.Send(reader.files) + } } else { // Create shared reader for periodic update reader, err = ts.currentWriter.CreatedSharedReader() @@ -300,13 +300,26 @@ func (ts *TreeStore) MarkOrphans(version uint32, nodeIds [][]NodeID) { } func (ts *TreeStore) syncProc() { + tick := time.NewTicker(time.Duration(ts.opts.FsyncInterval) * time.Millisecond) defer close(ts.syncDone) - for files := range ts.syncQueue { - if err := files.kvlogFile.Sync(); err != nil { + for { + <-tick.C + curWriter := ts.currentWriter + err := curWriter.SyncWAL() + if err != nil { ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err) return } - files.needsSync.Store(false) + needsSync, closed := ts.syncQueue.MaybeReceive() + for _, f := range needsSync { + if err := f.kvlogFile.Sync(); err != nil { + ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err) + return + } + } + if closed { + return + } } } @@ -314,7 +327,7 @@ func (ts *TreeStore) Close() error { ts.cleanupProc.shutdown() if ts.syncQueue != nil { - close(ts.syncQueue) + ts.syncQueue.Close() err := <-ts.syncDone if err != nil { return err From 9586547d6049bf59400399d11e3954ce80444cdc Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 13:05:20 -0400 Subject: [PATCH 28/87] fix queue --- iavl/tree_store.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/iavl/tree_store.go b/iavl/tree_store.go index 472b15cf5ef4..09b77c80933e 100644 --- a/iavl/tree_store.go +++ b/iavl/tree_store.go @@ -25,7 +25,7 @@ type TreeStore struct { opts Options - syncQueue *NonBlockingQueue[*ChangesetFiles] + syncQueue *NonBlockingQueue[*ChangesetWriter] syncDone chan error cleanupProc *cleanupProc @@ -56,7 +56,7 @@ func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, e ts.cleanupProc = newCleanupProc(ts) if options.WriteWAL && options.FsyncInterval > 0 { - ts.syncQueue = NewNonBlockingQueue[*ChangesetFiles]() + ts.syncQueue = NewNonBlockingQueue[*ChangesetWriter]() ts.syncDone = make(chan error) go ts.syncProc() } @@ -241,13 +241,14 @@ func (ts *TreeStore) SaveRoot(version uint32, root *NodePointer, totalLeaves, to var reader *Changeset if shouldSeal { // Size limit reached - seal the current batch - reader, err = ts.currentWriter.Seal() + curWriter := ts.currentWriter + reader, err = curWriter.Seal() if err != nil { return fmt.Errorf("failed to seal changeset for version %d: %w", version, err) } if ts.syncQueue != nil { // if sync queue is enabled, queue the files for fsync - ts.syncQueue.Send(reader.files) + ts.syncQueue.Send(curWriter) } } else { // Create shared reader for periodic update @@ -312,7 +313,7 @@ func (ts *TreeStore) syncProc() { } needsSync, closed := ts.syncQueue.MaybeReceive() for _, f := range needsSync { - if err := f.kvlogFile.Sync(); err != nil { + if err := f.SyncWAL(); err != nil { ts.syncDone <- fmt.Errorf("failed to sync WAL file: %w", err) return } From 20f05747716d9b431f46b846082b2d80b22aa83d Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 13:08:22 -0400 Subject: [PATCH 29/87] remove pond --- go.mod | 1 - go.sum | 2 -- iavl/commit_multi_tree.go | 46 +++++++++++++++++---------------------- simapp/go.mod | 1 - simapp/go.sum | 2 -- tests/go.mod | 1 - tests/go.sum | 2 -- tests/systemtests/go.mod | 1 - tests/systemtests/go.sum | 2 -- 9 files changed, 20 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index df8ad582b959..0f5e1f93a49e 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( cosmossdk.io/store v1.1.2 cosmossdk.io/x/tx v0.14.0 github.com/99designs/keyring v1.2.1 - github.com/alitto/pond/v2 v2.5.0 github.com/bgentry/speakeasy v0.2.0 github.com/bits-and-blooms/bitset v1.24.1 github.com/chzyer/readline v1.5.1 diff --git a/go.sum b/go.sum index cb6b440702d6..1b95067648a7 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= -github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index f04077c77158..efb1356bbda4 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -6,13 +6,13 @@ import ( io "io" "os" "path/filepath" - "runtime" + "sync" - "github.com/alitto/pond/v2" dbm "github.com/cosmos/cosmos-db" protoio "github.com/cosmos/gogoproto/io" "cosmossdk.io/log" + "cosmossdk.io/store/mem" "cosmossdk.io/store/metrics" pruningtypes "cosmossdk.io/store/pruning/types" @@ -32,7 +32,6 @@ type CommitMultiTree struct { version uint64 lastCommitId storetypes.CommitID - commitPool pond.ResultPool[[]byte] workingCommitInfo *storetypes.CommitInfo workingHash []byte } @@ -43,18 +42,17 @@ func (db *CommitMultiTree) LastCommitID() storetypes.CommitID { func (db *CommitMultiTree) WorkingHash() []byte { // NOTE: this may invoke some hash recomputation each time even if there is no change - taskGroup := db.commitPool.NewGroup() stagedVersion := db.version + 1 - for _, tree := range db.trees { - t := tree - taskGroup.Submit(func() []byte { - return t.WorkingHash() - }) - } - hashes, err := taskGroup.Wait() - if err != nil { - panic(fmt.Errorf("failed to commit trees: %w", err)) + hashes := make([][]byte, len(db.trees)) + var wg sync.WaitGroup + for i, tree := range db.trees { + wg.Add(1) + go func(i int, t storetypes.CommitKVStore) { + defer wg.Done() + hashes[i] = t.WorkingHash() + }(i, tree) } + wg.Wait() commitInfo := &storetypes.CommitInfo{} commitInfo.StoreInfos = make([]storetypes.StoreInfo, len(db.treeKeys)) @@ -78,19 +76,16 @@ func (db *CommitMultiTree) Commit() storetypes.CommitID { // and we're trying to avoid recomputing the hash // so we check if we already have a hash that was computed in WorkingHash that hasn't changed to avoid recomputation // in the future we should evaluate if there is any need to retain both WorkingHash and Commit methods separately - taskGroup := db.commitPool.NewGroup() - for _, tree := range db.trees { - t := tree - taskGroup.Submit(func() []byte { - commitId := t.Commit() - return commitId.Hash - }) - } - - hashes, err := taskGroup.Wait() - if err != nil { - panic(fmt.Errorf("failed to commit trees: %w", err)) + hashes := make([][]byte, len(db.trees)) + var wg sync.WaitGroup + for i, tree := range db.trees { + wg.Add(1) + go func(i int, t storetypes.CommitKVStore) { + defer wg.Done() + hashes[i] = t.Commit().Hash + }(i, tree) } + wg.Wait() stagedVersion := db.version + 1 commitInfo := db.workingCommitInfo @@ -377,7 +372,6 @@ func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, er db := &CommitMultiTree{ dir: path, opts: *opts, - commitPool: pond.NewResultPool[[]byte](runtime.NumCPU()), logger: logger, treesByKey: make(map[storetypes.StoreKey]int), } diff --git a/simapp/go.mod b/simapp/go.mod index f68a3a8c02cf..6f4dfcd4bdcc 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -46,7 +46,6 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect - github.com/alitto/pond/v2 v2.5.0 // indirect github.com/aws/aws-sdk-go-v2 v1.39.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.31.8 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index 28d46f5d7974..49f0efd62c68 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -84,8 +84,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= -github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= diff --git a/tests/go.mod b/tests/go.mod index f97046efd7df..569d1f12f569 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -49,7 +49,6 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect - github.com/alitto/pond/v2 v2.5.0 // indirect github.com/aws/aws-sdk-go-v2 v1.39.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.31.8 // indirect diff --git a/tests/go.sum b/tests/go.sum index 7b6a6bf9d326..edb45cb2a56e 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -82,8 +82,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= -github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index 4dc28c40c430..c6a22b4e7314 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -32,7 +32,6 @@ require ( github.com/99designs/keyring v1.2.2 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect - github.com/alitto/pond/v2 v2.5.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index 01a387d30d79..e24682075d0d 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -52,8 +52,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= -github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= From f5d3526335d481bf66a23ce029b1db71f86b5f6d Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 18:25:26 -0400 Subject: [PATCH 30/87] WIP on cachekv --- iavl/cache_tree.go | 159 ++++++++++++++++++++++++++++++++++++++++++++ iavl/commit_tree.go | 22 +++++- iavl/multi_tree.go | 10 ++- 3 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 iavl/cache_tree.go diff --git a/iavl/cache_tree.go b/iavl/cache_tree.go new file mode 100644 index 000000000000..40d0fb75d72f --- /dev/null +++ b/iavl/cache_tree.go @@ -0,0 +1,159 @@ +package iavlx + +import ( + io "io" + "sync" + + "github.com/tidwall/btree" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/internal/conv" +) + +type CacheTree struct { + mtx sync.Mutex // TODO do we really need a mutex or could this be part of the caller contract? + parent storetypes.KVStore + dirty bool + cache btree.Map[string, cacheEntry] +} + +func NewCacheTree(parent storetypes.KVStore) *CacheTree { + return &CacheTree{parent: parent} +} + +type cacheEntry struct { + value []byte + dirty bool +} + +func (store *CacheTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeIAVL +} + +func (store *CacheTree) CacheWrap() storetypes.CacheWrap { + return NewCacheTree(store) +} + +func (store *CacheTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO implement tracing + return NewCacheTree(store) +} + +func (store *CacheTree) Get(key []byte) (value []byte) { + store.mtx.Lock() + defer store.mtx.Unlock() + + storetypes.AssertValidKey(key) + + keyStr := conv.UnsafeBytesToStr(key) + cacheValue, ok := store.cache.Get(keyStr) + if !ok { + value = store.parent.Get(key) + store.setCacheValue(keyStr, value, false) + } else { + value = cacheValue.value + } + return value +} + +func (store *CacheTree) Has(key []byte) bool { + value := store.Get(key) + return value != nil +} + +func (store *CacheTree) Set(key, value []byte) { + storetypes.AssertValidKey(key) + storetypes.AssertValidValue(value) + + store.mtx.Lock() + defer store.mtx.Unlock() + store.setCacheValue(conv.UnsafeBytesToStr(key), value, true) +} + +func (store *CacheTree) Delete(key []byte) { + storetypes.AssertValidKey(key) + + store.mtx.Lock() + defer store.mtx.Unlock() + + store.setCacheValue(conv.UnsafeBytesToStr(key), nil, true) +} + +func (store *CacheTree) Iterator(start, end []byte) storetypes.Iterator { + //TODO implement me + panic("implement me") +} + +func (store *CacheTree) ReverseIterator(start, end []byte) storetypes.Iterator { + //TODO implement me + panic("implement me") +} + +func (store *CacheTree) Write() { + store.mtx.Lock() + defer store.mtx.Unlock() + + if !store.dirty { + return + } + + // TODO if we are concerned about retaining the whole tree in memory, we could maybe drain the cache using Map.PopMin + store.cache.Scan(func(key string, value cacheEntry) bool { + if !value.dirty { + // TODO we could save these cached reads in the tree but for now we just clear the whole cache + return true + } + + // We use []byte(key) instead of conv.UnsafeStrToBytes because we cannot + // be sure if the underlying store might do a save with the byteslice or + // not. Once we get confirmation that .Delete is guaranteed not to + // save the byteslice, then we can assume only a read-only copy is sufficient. + if value.value == nil { + store.parent.Delete([]byte(key)) + } else { + store.parent.Set([]byte(key), value.value) + } + return true + }) + + store.cache.Clear() + store.dirty = false +} + +func (store *CacheTree) setCacheValue(key string, value []byte, dirty bool) { + if dirty { + store.dirty = true + } + store.cache.Set(key, cacheEntry{ + value: value, + dirty: dirty, + }) +} + +func (store *CacheTree) iterator(start, end []byte, ascending bool) types.Iterator { + store.mtx.Lock() + defer store.mtx.Unlock() + + store.dirtyItems(start, end) + isoSortedCache := store.sortedCache.Copy() + + var ( + err error + parent, cache types.Iterator + ) + + if ascending { + parent = store.parent.Iterator(start, end) + cache, err = isoSortedCache.Iterator(start, end) + } else { + parent = store.parent.ReverseIterator(start, end) + cache, err = isoSortedCache.ReverseIterator(start, end) + } + if err != nil { + panic(err) + } + + return internal.NewCacheMergeIterator(parent, cache, ascending) +} + +var _ storetypes.CacheKVStore = (*CacheTree)(nil) diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index b17b53df2b01..da569ca674af 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -209,9 +209,25 @@ func (c *CommitTree) Has(key []byte) bool { } func (c *CommitTree) Set(key, value []byte) { - tree := c.CacheWrap().(*Tree) - tree.Set(key, value) - tree.Write() + stagedVersion := c.stagedVersion() + leafNode := &MemNode{ + height: 0, + size: 1, + version: stagedVersion, + key: key, + value: value, + } + ctx := &MutationContext{Version: stagedVersion} + newRoot, _, err := setRecursive(c.root, leafNode, ctx) + if err != nil { + panic(err) + } + + c.root = newRoot + tree.updateBatch.Updates = append(tree.updateBatch.Updates, KVUpdate{ + SetNode: leafNode, + }) + tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) } func (c *CommitTree) Delete(key []byte) { diff --git a/iavl/multi_tree.go b/iavl/multi_tree.go index dfe4858a6785..c958bc4aa493 100644 --- a/iavl/multi_tree.go +++ b/iavl/multi_tree.go @@ -3,6 +3,7 @@ package iavlx import ( "fmt" io "io" + "sync" storetypes "cosmossdk.io/store/types" ) @@ -14,9 +15,16 @@ type MultiTree struct { } func (t *MultiTree) Write() { + var wg sync.WaitGroup for _, tree := range t.trees { - tree.Write() + // TODO check if trees are dirty before spinning off a goroutine + wg.Add(1) + go func(t storetypes.CacheKVStore) { + defer wg.Done() + t.Write() + }(tree) } + wg.Wait() } func (t *MultiTree) GetStoreType() storetypes.StoreType { From e98ec2e86fbcc3d119fb9865ab7c6a0647e0037d Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 18:25:59 -0400 Subject: [PATCH 31/87] move to store/iavlx --- {iavl => store/iavlx}/README.md | 0 {iavl => store/iavlx}/branch_layout.go | 0 {iavl => store/iavlx}/branch_persisted.go | 0 {iavl => store/iavlx}/cache_tree.go | 0 {iavl => store/iavlx}/changeset.go | 0 {iavl => store/iavlx}/changeset_files.go | 0 {iavl => store/iavlx}/changeset_info.go | 0 {iavl => store/iavlx}/changeset_writer.go | 0 {iavl => store/iavlx}/cleanup.go | 0 {iavl => store/iavlx}/commit_multi_tree.go | 0 {iavl => store/iavlx}/commit_tree.go | 0 {iavl => store/iavlx}/compactor.go | 0 {iavl => store/iavlx}/dot_graph.go | 0 {iavl => store/iavlx}/iterator.go | 0 {iavl => store/iavlx}/kvlog.go | 0 {iavl => store/iavlx}/kvlog_writer.go | 0 {iavl => store/iavlx}/leaf_layout.go | 0 {iavl => store/iavlx}/leaf_persisted.go | 0 {iavl => store/iavlx}/mem_node.go | 0 {iavl => store/iavlx}/mmap.go | 0 {iavl => store/iavlx}/multi_tree.go | 0 {iavl => store/iavlx}/node.go | 0 {iavl => store/iavlx}/node_hash.go | 0 {iavl => store/iavlx}/node_id.go | 0 {iavl => store/iavlx}/node_pointer.go | 0 {iavl => store/iavlx}/node_update.go | 0 {iavl => store/iavlx}/non_blocking_queue.go | 0 {iavl => store/iavlx}/options.go | 0 {iavl => store/iavlx}/reader.go | 0 {iavl => store/iavlx}/tree.go | 0 {iavl => store/iavlx}/tree_store.go | 0 {iavl => store/iavlx}/tree_test.go | 0 {iavl => store/iavlx}/update.go | 0 {iavl => store/iavlx}/verify.go | 0 {iavl => store/iavlx}/version_info.go | 0 {iavl => store/iavlx}/writer.go | 0 36 files changed, 0 insertions(+), 0 deletions(-) rename {iavl => store/iavlx}/README.md (100%) rename {iavl => store/iavlx}/branch_layout.go (100%) rename {iavl => store/iavlx}/branch_persisted.go (100%) rename {iavl => store/iavlx}/cache_tree.go (100%) rename {iavl => store/iavlx}/changeset.go (100%) rename {iavl => store/iavlx}/changeset_files.go (100%) rename {iavl => store/iavlx}/changeset_info.go (100%) rename {iavl => store/iavlx}/changeset_writer.go (100%) rename {iavl => store/iavlx}/cleanup.go (100%) rename {iavl => store/iavlx}/commit_multi_tree.go (100%) rename {iavl => store/iavlx}/commit_tree.go (100%) rename {iavl => store/iavlx}/compactor.go (100%) rename {iavl => store/iavlx}/dot_graph.go (100%) rename {iavl => store/iavlx}/iterator.go (100%) rename {iavl => store/iavlx}/kvlog.go (100%) rename {iavl => store/iavlx}/kvlog_writer.go (100%) rename {iavl => store/iavlx}/leaf_layout.go (100%) rename {iavl => store/iavlx}/leaf_persisted.go (100%) rename {iavl => store/iavlx}/mem_node.go (100%) rename {iavl => store/iavlx}/mmap.go (100%) rename {iavl => store/iavlx}/multi_tree.go (100%) rename {iavl => store/iavlx}/node.go (100%) rename {iavl => store/iavlx}/node_hash.go (100%) rename {iavl => store/iavlx}/node_id.go (100%) rename {iavl => store/iavlx}/node_pointer.go (100%) rename {iavl => store/iavlx}/node_update.go (100%) rename {iavl => store/iavlx}/non_blocking_queue.go (100%) rename {iavl => store/iavlx}/options.go (100%) rename {iavl => store/iavlx}/reader.go (100%) rename {iavl => store/iavlx}/tree.go (100%) rename {iavl => store/iavlx}/tree_store.go (100%) rename {iavl => store/iavlx}/tree_test.go (100%) rename {iavl => store/iavlx}/update.go (100%) rename {iavl => store/iavlx}/verify.go (100%) rename {iavl => store/iavlx}/version_info.go (100%) rename {iavl => store/iavlx}/writer.go (100%) diff --git a/iavl/README.md b/store/iavlx/README.md similarity index 100% rename from iavl/README.md rename to store/iavlx/README.md diff --git a/iavl/branch_layout.go b/store/iavlx/branch_layout.go similarity index 100% rename from iavl/branch_layout.go rename to store/iavlx/branch_layout.go diff --git a/iavl/branch_persisted.go b/store/iavlx/branch_persisted.go similarity index 100% rename from iavl/branch_persisted.go rename to store/iavlx/branch_persisted.go diff --git a/iavl/cache_tree.go b/store/iavlx/cache_tree.go similarity index 100% rename from iavl/cache_tree.go rename to store/iavlx/cache_tree.go diff --git a/iavl/changeset.go b/store/iavlx/changeset.go similarity index 100% rename from iavl/changeset.go rename to store/iavlx/changeset.go diff --git a/iavl/changeset_files.go b/store/iavlx/changeset_files.go similarity index 100% rename from iavl/changeset_files.go rename to store/iavlx/changeset_files.go diff --git a/iavl/changeset_info.go b/store/iavlx/changeset_info.go similarity index 100% rename from iavl/changeset_info.go rename to store/iavlx/changeset_info.go diff --git a/iavl/changeset_writer.go b/store/iavlx/changeset_writer.go similarity index 100% rename from iavl/changeset_writer.go rename to store/iavlx/changeset_writer.go diff --git a/iavl/cleanup.go b/store/iavlx/cleanup.go similarity index 100% rename from iavl/cleanup.go rename to store/iavlx/cleanup.go diff --git a/iavl/commit_multi_tree.go b/store/iavlx/commit_multi_tree.go similarity index 100% rename from iavl/commit_multi_tree.go rename to store/iavlx/commit_multi_tree.go diff --git a/iavl/commit_tree.go b/store/iavlx/commit_tree.go similarity index 100% rename from iavl/commit_tree.go rename to store/iavlx/commit_tree.go diff --git a/iavl/compactor.go b/store/iavlx/compactor.go similarity index 100% rename from iavl/compactor.go rename to store/iavlx/compactor.go diff --git a/iavl/dot_graph.go b/store/iavlx/dot_graph.go similarity index 100% rename from iavl/dot_graph.go rename to store/iavlx/dot_graph.go diff --git a/iavl/iterator.go b/store/iavlx/iterator.go similarity index 100% rename from iavl/iterator.go rename to store/iavlx/iterator.go diff --git a/iavl/kvlog.go b/store/iavlx/kvlog.go similarity index 100% rename from iavl/kvlog.go rename to store/iavlx/kvlog.go diff --git a/iavl/kvlog_writer.go b/store/iavlx/kvlog_writer.go similarity index 100% rename from iavl/kvlog_writer.go rename to store/iavlx/kvlog_writer.go diff --git a/iavl/leaf_layout.go b/store/iavlx/leaf_layout.go similarity index 100% rename from iavl/leaf_layout.go rename to store/iavlx/leaf_layout.go diff --git a/iavl/leaf_persisted.go b/store/iavlx/leaf_persisted.go similarity index 100% rename from iavl/leaf_persisted.go rename to store/iavlx/leaf_persisted.go diff --git a/iavl/mem_node.go b/store/iavlx/mem_node.go similarity index 100% rename from iavl/mem_node.go rename to store/iavlx/mem_node.go diff --git a/iavl/mmap.go b/store/iavlx/mmap.go similarity index 100% rename from iavl/mmap.go rename to store/iavlx/mmap.go diff --git a/iavl/multi_tree.go b/store/iavlx/multi_tree.go similarity index 100% rename from iavl/multi_tree.go rename to store/iavlx/multi_tree.go diff --git a/iavl/node.go b/store/iavlx/node.go similarity index 100% rename from iavl/node.go rename to store/iavlx/node.go diff --git a/iavl/node_hash.go b/store/iavlx/node_hash.go similarity index 100% rename from iavl/node_hash.go rename to store/iavlx/node_hash.go diff --git a/iavl/node_id.go b/store/iavlx/node_id.go similarity index 100% rename from iavl/node_id.go rename to store/iavlx/node_id.go diff --git a/iavl/node_pointer.go b/store/iavlx/node_pointer.go similarity index 100% rename from iavl/node_pointer.go rename to store/iavlx/node_pointer.go diff --git a/iavl/node_update.go b/store/iavlx/node_update.go similarity index 100% rename from iavl/node_update.go rename to store/iavlx/node_update.go diff --git a/iavl/non_blocking_queue.go b/store/iavlx/non_blocking_queue.go similarity index 100% rename from iavl/non_blocking_queue.go rename to store/iavlx/non_blocking_queue.go diff --git a/iavl/options.go b/store/iavlx/options.go similarity index 100% rename from iavl/options.go rename to store/iavlx/options.go diff --git a/iavl/reader.go b/store/iavlx/reader.go similarity index 100% rename from iavl/reader.go rename to store/iavlx/reader.go diff --git a/iavl/tree.go b/store/iavlx/tree.go similarity index 100% rename from iavl/tree.go rename to store/iavlx/tree.go diff --git a/iavl/tree_store.go b/store/iavlx/tree_store.go similarity index 100% rename from iavl/tree_store.go rename to store/iavlx/tree_store.go diff --git a/iavl/tree_test.go b/store/iavlx/tree_test.go similarity index 100% rename from iavl/tree_test.go rename to store/iavlx/tree_test.go diff --git a/iavl/update.go b/store/iavlx/update.go similarity index 100% rename from iavl/update.go rename to store/iavlx/update.go diff --git a/iavl/verify.go b/store/iavlx/verify.go similarity index 100% rename from iavl/verify.go rename to store/iavlx/verify.go diff --git a/iavl/version_info.go b/store/iavlx/version_info.go similarity index 100% rename from iavl/version_info.go rename to store/iavlx/version_info.go diff --git a/iavl/writer.go b/store/iavlx/writer.go similarity index 100% rename from iavl/writer.go rename to store/iavlx/writer.go From 5cc83ef026891182dadfab28951dd60fee84b366 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 18:57:44 -0400 Subject: [PATCH 32/87] add cachekv for iavlx --- {store/iavlx => iavlx}/README.md | 0 {store/iavlx => iavlx}/branch_layout.go | 0 {store/iavlx => iavlx}/branch_persisted.go | 0 {store/iavlx => iavlx}/cache_tree.go | 60 ++--- {store/iavlx => iavlx}/changeset.go | 0 {store/iavlx => iavlx}/changeset_files.go | 0 {store/iavlx => iavlx}/changeset_info.go | 0 {store/iavlx => iavlx}/changeset_writer.go | 0 {store/iavlx => iavlx}/cleanup.go | 0 {store/iavlx => iavlx}/commit_multi_tree.go | 4 +- {store/iavlx => iavlx}/commit_tree.go | 65 +++-- {store/iavlx => iavlx}/compactor.go | 0 {store/iavlx => iavlx}/dot_graph.go | 0 iavlx/immutable_tree.go | 75 ++++++ iavlx/internal/btree.go | 106 +++++++++ iavlx/internal/btree_test.go | 204 ++++++++++++++++ iavlx/internal/memiterator.go | 120 ++++++++++ iavlx/internal/mergeiterator.go | 235 +++++++++++++++++++ {store/iavlx => iavlx}/iterator.go | 0 {store/iavlx => iavlx}/kvlog.go | 0 {store/iavlx => iavlx}/kvlog_writer.go | 0 {store/iavlx => iavlx}/leaf_layout.go | 0 {store/iavlx => iavlx}/leaf_persisted.go | 0 {store/iavlx => iavlx}/mem_node.go | 0 {store/iavlx => iavlx}/mmap.go | 0 {store/iavlx => iavlx}/multi_tree.go | 0 {store/iavlx => iavlx}/node.go | 0 {store/iavlx => iavlx}/node_hash.go | 0 {store/iavlx => iavlx}/node_id.go | 0 {store/iavlx => iavlx}/node_pointer.go | 0 {store/iavlx => iavlx}/node_update.go | 0 {store/iavlx => iavlx}/non_blocking_queue.go | 0 {store/iavlx => iavlx}/options.go | 0 {store/iavlx => iavlx}/reader.go | 0 {store/iavlx => iavlx}/tree_store.go | 0 {store/iavlx => iavlx}/tree_test.go | 18 +- {store/iavlx => iavlx}/update.go | 0 {store/iavlx => iavlx}/verify.go | 0 {store/iavlx => iavlx}/version_info.go | 0 {store/iavlx => iavlx}/writer.go | 0 store/iavlx/tree.go | 144 ------------ 41 files changed, 803 insertions(+), 228 deletions(-) rename {store/iavlx => iavlx}/README.md (100%) rename {store/iavlx => iavlx}/branch_layout.go (100%) rename {store/iavlx => iavlx}/branch_persisted.go (100%) rename {store/iavlx => iavlx}/cache_tree.go (71%) rename {store/iavlx => iavlx}/changeset.go (100%) rename {store/iavlx => iavlx}/changeset_files.go (100%) rename {store/iavlx => iavlx}/changeset_info.go (100%) rename {store/iavlx => iavlx}/changeset_writer.go (100%) rename {store/iavlx => iavlx}/cleanup.go (100%) rename {store/iavlx => iavlx}/commit_multi_tree.go (98%) rename {store/iavlx => iavlx}/commit_tree.go (89%) rename {store/iavlx => iavlx}/compactor.go (100%) rename {store/iavlx => iavlx}/dot_graph.go (100%) create mode 100644 iavlx/immutable_tree.go create mode 100644 iavlx/internal/btree.go create mode 100644 iavlx/internal/btree_test.go create mode 100644 iavlx/internal/memiterator.go create mode 100644 iavlx/internal/mergeiterator.go rename {store/iavlx => iavlx}/iterator.go (100%) rename {store/iavlx => iavlx}/kvlog.go (100%) rename {store/iavlx => iavlx}/kvlog_writer.go (100%) rename {store/iavlx => iavlx}/leaf_layout.go (100%) rename {store/iavlx => iavlx}/leaf_persisted.go (100%) rename {store/iavlx => iavlx}/mem_node.go (100%) rename {store/iavlx => iavlx}/mmap.go (100%) rename {store/iavlx => iavlx}/multi_tree.go (100%) rename {store/iavlx => iavlx}/node.go (100%) rename {store/iavlx => iavlx}/node_hash.go (100%) rename {store/iavlx => iavlx}/node_id.go (100%) rename {store/iavlx => iavlx}/node_pointer.go (100%) rename {store/iavlx => iavlx}/node_update.go (100%) rename {store/iavlx => iavlx}/non_blocking_queue.go (100%) rename {store/iavlx => iavlx}/options.go (100%) rename {store/iavlx => iavlx}/reader.go (100%) rename {store/iavlx => iavlx}/tree_store.go (100%) rename {store/iavlx => iavlx}/tree_test.go (95%) rename {store/iavlx => iavlx}/update.go (100%) rename {store/iavlx => iavlx}/verify.go (100%) rename {store/iavlx => iavlx}/version_info.go (100%) rename {store/iavlx => iavlx}/writer.go (100%) delete mode 100644 store/iavlx/tree.go diff --git a/store/iavlx/README.md b/iavlx/README.md similarity index 100% rename from store/iavlx/README.md rename to iavlx/README.md diff --git a/store/iavlx/branch_layout.go b/iavlx/branch_layout.go similarity index 100% rename from store/iavlx/branch_layout.go rename to iavlx/branch_layout.go diff --git a/store/iavlx/branch_persisted.go b/iavlx/branch_persisted.go similarity index 100% rename from store/iavlx/branch_persisted.go rename to iavlx/branch_persisted.go diff --git a/store/iavlx/cache_tree.go b/iavlx/cache_tree.go similarity index 71% rename from store/iavlx/cache_tree.go rename to iavlx/cache_tree.go index 40d0fb75d72f..53550442ebaf 100644 --- a/store/iavlx/cache_tree.go +++ b/iavlx/cache_tree.go @@ -4,26 +4,19 @@ import ( io "io" "sync" - "github.com/tidwall/btree" - storetypes "cosmossdk.io/store/types" - "github.com/cosmos/cosmos-sdk/internal/conv" + "github.com/cosmos/cosmos-sdk/iavlx/internal" ) type CacheTree struct { mtx sync.Mutex // TODO do we really need a mutex or could this be part of the caller contract? parent storetypes.KVStore dirty bool - cache btree.Map[string, cacheEntry] + cache internal.BTree } func NewCacheTree(parent storetypes.KVStore) *CacheTree { - return &CacheTree{parent: parent} -} - -type cacheEntry struct { - value []byte - dirty bool + return &CacheTree{parent: parent, cache: internal.NewBTree()} } func (store *CacheTree) GetStoreType() storetypes.StoreType { @@ -45,13 +38,11 @@ func (store *CacheTree) Get(key []byte) (value []byte) { storetypes.AssertValidKey(key) - keyStr := conv.UnsafeBytesToStr(key) - cacheValue, ok := store.cache.Get(keyStr) + var ok bool + value, ok = store.cache.Get(key) if !ok { value = store.parent.Get(key) - store.setCacheValue(keyStr, value, false) - } else { - value = cacheValue.value + store.cache.SetCached(key, value) } return value } @@ -67,7 +58,8 @@ func (store *CacheTree) Set(key, value []byte) { store.mtx.Lock() defer store.mtx.Unlock() - store.setCacheValue(conv.UnsafeBytesToStr(key), value, true) + store.cache.Set(key, value) + store.dirty = true } func (store *CacheTree) Delete(key []byte) { @@ -76,17 +68,16 @@ func (store *CacheTree) Delete(key []byte) { store.mtx.Lock() defer store.mtx.Unlock() - store.setCacheValue(conv.UnsafeBytesToStr(key), nil, true) + store.cache.Delete(key) + store.dirty = true } func (store *CacheTree) Iterator(start, end []byte) storetypes.Iterator { - //TODO implement me - panic("implement me") + return store.iterator(start, end, true) } func (store *CacheTree) ReverseIterator(start, end []byte) storetypes.Iterator { - //TODO implement me - panic("implement me") + return store.iterator(start, end, false) } func (store *CacheTree) Write() { @@ -98,8 +89,8 @@ func (store *CacheTree) Write() { } // TODO if we are concerned about retaining the whole tree in memory, we could maybe drain the cache using Map.PopMin - store.cache.Scan(func(key string, value cacheEntry) bool { - if !value.dirty { + store.cache.Scan(func(key, value []byte, dirty bool) bool { + if !dirty { // TODO we could save these cached reads in the tree but for now we just clear the whole cache return true } @@ -108,10 +99,10 @@ func (store *CacheTree) Write() { // be sure if the underlying store might do a save with the byteslice or // not. Once we get confirmation that .Delete is guaranteed not to // save the byteslice, then we can assume only a read-only copy is sufficient. - if value.value == nil { - store.parent.Delete([]byte(key)) + if value == nil { + store.parent.Delete(key) } else { - store.parent.Set([]byte(key), value.value) + store.parent.Set(key, value) } return true }) @@ -120,26 +111,15 @@ func (store *CacheTree) Write() { store.dirty = false } -func (store *CacheTree) setCacheValue(key string, value []byte, dirty bool) { - if dirty { - store.dirty = true - } - store.cache.Set(key, cacheEntry{ - value: value, - dirty: dirty, - }) -} - -func (store *CacheTree) iterator(start, end []byte, ascending bool) types.Iterator { +func (store *CacheTree) iterator(start, end []byte, ascending bool) storetypes.Iterator { store.mtx.Lock() defer store.mtx.Unlock() - store.dirtyItems(start, end) - isoSortedCache := store.sortedCache.Copy() + isoSortedCache := store.cache.Copy() var ( err error - parent, cache types.Iterator + parent, cache storetypes.Iterator ) if ascending { diff --git a/store/iavlx/changeset.go b/iavlx/changeset.go similarity index 100% rename from store/iavlx/changeset.go rename to iavlx/changeset.go diff --git a/store/iavlx/changeset_files.go b/iavlx/changeset_files.go similarity index 100% rename from store/iavlx/changeset_files.go rename to iavlx/changeset_files.go diff --git a/store/iavlx/changeset_info.go b/iavlx/changeset_info.go similarity index 100% rename from store/iavlx/changeset_info.go rename to iavlx/changeset_info.go diff --git a/store/iavlx/changeset_writer.go b/iavlx/changeset_writer.go similarity index 100% rename from store/iavlx/changeset_writer.go rename to iavlx/changeset_writer.go diff --git a/store/iavlx/cleanup.go b/iavlx/cleanup.go similarity index 100% rename from store/iavlx/cleanup.go rename to iavlx/cleanup.go diff --git a/store/iavlx/commit_multi_tree.go b/iavlx/commit_multi_tree.go similarity index 98% rename from store/iavlx/commit_multi_tree.go rename to iavlx/commit_multi_tree.go index efb1356bbda4..8e55d5ca7e3a 100644 --- a/store/iavlx/commit_multi_tree.go +++ b/iavlx/commit_multi_tree.go @@ -177,10 +177,12 @@ func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes switch typ { case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: var err error - mt.trees[i], err = tree.(*CommitTree).GetImmutable(version) + var t storetypes.KVStore + t, err = tree.(*CommitTree).GetImmutable(version) if err != nil { return nil, fmt.Errorf("failed to create cache multi store for tree %s at version %d: %w", db.treeKeys[i].Name(), version, err) } + mt.trees[i] = t.CacheWrap().(storetypes.CacheKVStore) case storetypes.StoreTypeTransient, storetypes.StoreTypeMemory: mt.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) default: diff --git a/store/iavlx/commit_tree.go b/iavlx/commit_tree.go similarity index 89% rename from store/iavlx/commit_tree.go rename to iavlx/commit_tree.go index da569ca674af..f2ff1548e5f4 100644 --- a/store/iavlx/commit_tree.go +++ b/iavlx/commit_tree.go @@ -40,28 +40,6 @@ func (c *CommitTree) getRoot() *NodePointer { return c.root } -func (c *CommitTree) applyChangesToParent(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { - // TODO check channel errors - c.writeMutex.Lock() - defer c.writeMutex.Unlock() - - if updateBatch.Version != c.stagedVersion() { - return fmt.Errorf("tree version %d does not match staged version %d", updateBatch.Version, c.stagedVersion()) - } - if origRoot != c.root { - // TODO find a way to apply the changes incrementally when roots don't match - return fmt.Errorf("tree original root does not match current root") - } - c.root = newRoot - c.pendingOrphans = append(c.pendingOrphans, updateBatch.Orphans...) - - if c.writeWal { - c.walQueue.Send(updateBatch.Updates) - } - - return nil -} - func (c *CommitTree) WorkingHash() []byte { c.writeMutex.Lock() defer c.writeMutex.Unlock() @@ -178,7 +156,7 @@ func (c *CommitTree) GetStoreType() storetypes.StoreType { } func (c *CommitTree) CacheWrap() storetypes.CacheWrap { - return NewTree(c, c.stagedVersion(), c.zeroCopy) + return NewCacheTree(c) } func (c *CommitTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { @@ -209,6 +187,12 @@ func (c *CommitTree) Has(key []byte) bool { } func (c *CommitTree) Set(key, value []byte) { + storetypes.AssertValidKey(key) + storetypes.AssertValidValue(value) + + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + stagedVersion := c.stagedVersion() leafNode := &MemNode{ height: 0, @@ -224,16 +208,30 @@ func (c *CommitTree) Set(key, value []byte) { } c.root = newRoot - tree.updateBatch.Updates = append(tree.updateBatch.Updates, KVUpdate{ - SetNode: leafNode, - }) - tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) + c.applyUpdates([]KVUpdate{{SetNode: leafNode}}, ctx.Orphans) } func (c *CommitTree) Delete(key []byte) { - tree := c.CacheWrap().(*Tree) - tree.Delete(key) - tree.Write() + storetypes.AssertValidKey(key) + + c.writeMutex.Lock() + defer c.writeMutex.Unlock() + + ctx := &MutationContext{Version: c.stagedVersion()} + _, newRoot, _, err := removeRecursive(c.root, key, ctx) + if err != nil { + panic(err) + } + c.root = newRoot + c.applyUpdates([]KVUpdate{{DeleteKey: key}}, ctx.Orphans) +} + +func (c *CommitTree) applyUpdates(updates []KVUpdate, orphans []NodeID) { + c.pendingOrphans = append(c.pendingOrphans, orphans) + + if c.writeWal { + c.walQueue.Send(updates) + } } func (c *CommitTree) Iterator(start, end []byte) storetypes.Iterator { @@ -323,7 +321,7 @@ func (c *CommitTree) startEvict(evictVersion uint32) { }() } -func (c *CommitTree) GetImmutable(version int64) (storetypes.CacheKVStore, error) { +func (c *CommitTree) GetImmutable(version int64) (storetypes.KVStore, error) { var rootPtr *NodePointer if version == c.lastCommitId.Version { rootPtr = c.root @@ -334,9 +332,7 @@ func (c *CommitTree) GetImmutable(version int64) (storetypes.CacheKVStore, error return nil, err } } - return &Tree{ - root: rootPtr, - }, nil + return NewImmutableTree(rootPtr), nil } func (c *CommitTree) Close() error { @@ -424,5 +420,4 @@ func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uin var ( _ storetypes.CommitKVStore = &CommitTree{} - _ parentTree = &CommitTree{} ) diff --git a/store/iavlx/compactor.go b/iavlx/compactor.go similarity index 100% rename from store/iavlx/compactor.go rename to iavlx/compactor.go diff --git a/store/iavlx/dot_graph.go b/iavlx/dot_graph.go similarity index 100% rename from store/iavlx/dot_graph.go rename to iavlx/dot_graph.go diff --git a/iavlx/immutable_tree.go b/iavlx/immutable_tree.go new file mode 100644 index 000000000000..0e5aa4ee387d --- /dev/null +++ b/iavlx/immutable_tree.go @@ -0,0 +1,75 @@ +package iavlx + +import ( + io "io" + + corestore "cosmossdk.io/core/store" + + storetypes "cosmossdk.io/store/types" +) + +type ImmutableTree struct { + root *NodePointer +} + +func NewImmutableTree(root *NodePointer) *ImmutableTree { + return &ImmutableTree{ + root: root, + } +} + +func (tree *ImmutableTree) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeIAVL +} + +func (tree *ImmutableTree) CacheWrap() storetypes.CacheWrap { + return NewCacheTree(tree) +} + +func (tree *ImmutableTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + // TODO support tracing + return tree.CacheWrap() +} + +func (tree *ImmutableTree) Get(key []byte) []byte { + if tree.root == nil { + return nil + } + + root, err := tree.root.Resolve() + if err != nil { + panic(err) + } + + value, _, err := root.Get(key) + if err != nil { + panic(err) + } + + return value +} + +func (tree *ImmutableTree) Set(key, value []byte) { + panic("cannot set in immutable tree") +} + +func (tree *ImmutableTree) Delete(key []byte) { + panic("cannot delete from immutable tree") +} + +func (tree *ImmutableTree) Has(key []byte) bool { + val := tree.Get(key) + return val != nil +} + +func (tree *ImmutableTree) Iterator(start, end []byte) corestore.Iterator { + return NewIterator(start, end, true, tree.root, true) +} + +func (tree *ImmutableTree) ReverseIterator(start, end []byte) corestore.Iterator { + return NewIterator(start, end, false, tree.root, true) +} + +var ( + _ storetypes.KVStore = (*ImmutableTree)(nil) +) diff --git a/iavlx/internal/btree.go b/iavlx/internal/btree.go new file mode 100644 index 000000000000..8f0b78b197ed --- /dev/null +++ b/iavlx/internal/btree.go @@ -0,0 +1,106 @@ +package internal + +import ( + "bytes" + "errors" + + "github.com/tidwall/btree" + + "cosmossdk.io/store/types" +) + +const ( + // The approximate number of items and children per B-tree node. Tuned with benchmarks. + // copied from memdb. + bTreeDegree = 32 +) + +var errKeyEmpty = errors.New("key cannot be empty") + +// BTree implements the sorted cache for cachekv store, +// we don't use MemDB here because cachekv is used extensively in sdk core path, +// we need it to be as fast as possible, while `MemDB` is mainly used as a mocking db in unit tests. +// +// We choose tidwall/btree over google/btree here because it provides API to implement step iterator directly. +type BTree struct { + tree *btree.BTreeG[item] +} + +// NewBTree creates a wrapper around `btree.BTreeG`. +func NewBTree() BTree { + return BTree{ + tree: btree.NewBTreeGOptions(byKeys, btree.Options{ + Degree: bTreeDegree, + NoLocks: false, + }), + } +} + +func (bt BTree) Set(key, value []byte) { + bt.tree.Set(newItem(key, value, true)) +} + +func (bt BTree) SetCached(key, value []byte) { + bt.tree.Set(newItem(key, value, false)) +} + +func (bt BTree) Get(key []byte) ([]byte, bool) { + i, found := bt.tree.Get(newItem(key, nil, false)) + if !found { + return nil, false + } + return i.value, true +} + +func (bt BTree) Delete(key []byte) { + bt.tree.Set(newItem(key, nil, true)) +} + +func (bt BTree) Iterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemIterator(start, end, bt, true), nil +} + +func (bt BTree) ReverseIterator(start, end []byte) (types.Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemIterator(start, end, bt, false), nil +} + +func (bt BTree) Scan(f func(key []byte, value []byte, dirty bool) bool) { + bt.tree.Scan(func(item item) bool { + return f(item.key, item.value, item.dirty) + }) +} + +// Copy the tree. This is a copy-on-write operation and is very fast because +// it only performs a shadowed copy. +func (bt BTree) Copy() BTree { + return BTree{ + tree: bt.tree.Copy(), + } +} + +func (bt BTree) Clear() { + bt.tree.Clear() +} + +// item is a btree item with byte slices as keys and values +type item struct { + key []byte + value []byte + dirty bool +} + +// byKeys compares the items by key +func byKeys(a, b item) bool { + return bytes.Compare(a.key, b.key) == -1 +} + +// newItem creates a new pair item. +func newItem(key, value []byte, dirty bool) item { + return item{key: key, value: value, dirty: dirty} +} diff --git a/iavlx/internal/btree_test.go b/iavlx/internal/btree_test.go new file mode 100644 index 000000000000..50fa74c56a74 --- /dev/null +++ b/iavlx/internal/btree_test.go @@ -0,0 +1,204 @@ +package internal + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/store/types" +) + +func TestGetSetDelete(t *testing.T) { + db := NewBTree() + + // A nonexistent key should return nil. + value, _ := db.Get([]byte("a")) + require.Nil(t, value) + + // Set and get a value. + db.Set([]byte("a"), []byte{0x01}) + db.Set([]byte("b"), []byte{0x02}) + value, _ = db.Get([]byte("a")) + require.Equal(t, []byte{0x01}, value) + + value, _ = db.Get([]byte("b")) + require.Equal(t, []byte{0x02}, value) + + // Deleting a non-existent value is fine. + db.Delete([]byte("x")) + + // Delete a value. + db.Delete([]byte("a")) + + value, _ = db.Get([]byte("a")) + require.Nil(t, value) + + db.Delete([]byte("b")) + + value, _ = db.Get([]byte("b")) + require.Nil(t, value) +} + +func TestDBIterator(t *testing.T) { + db := NewBTree() + + for i := 0; i < 10; i++ { + if i != 6 { // but skip 6. + db.Set(int642Bytes(int64(i)), []byte{}) + } + } + + // Blank iterator keys should error + _, err := db.ReverseIterator([]byte{}, nil) + require.Equal(t, errKeyEmpty, err) + _, err = db.ReverseIterator(nil, []byte{}) + require.Equal(t, errKeyEmpty, err) + + itr, err := db.Iterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator") + + ritr, err := db.ReverseIterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator") + + itr, err = db.Iterator(nil, int642Bytes(0)) + require.NoError(t, err) + verifyIterator(t, itr, []int64(nil), "forward iterator to 0") + + ritr, err = db.ReverseIterator(int642Bytes(10), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64(nil), "reverse iterator from 10 (ex)") + + itr, err = db.Iterator(int642Bytes(0), nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0") + + itr, err = db.Iterator(int642Bytes(1), nil) + require.NoError(t, err) + verifyIterator(t, itr, []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1") + + ritr, err = db.ReverseIterator(nil, int642Bytes(10)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)") + + ritr, err = db.ReverseIterator(nil, int642Bytes(9)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") + + ritr, err = db.ReverseIterator(nil, int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 6") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5}, "forward iterator from 5 to 7") + + itr, err = db.Iterator(int642Bytes(5), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{5, 7}, "forward iterator from 5 to 8") + + itr, err = db.Iterator(int642Bytes(6), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, itr, []int64(nil), "forward iterator from 6 to 7") + + itr, err = db.Iterator(int642Bytes(6), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{7}, "forward iterator from 6 to 8") + + itr, err = db.Iterator(int642Bytes(7), int642Bytes(8)) + require.NoError(t, err) + verifyIterator(t, itr, []int64{7}, "forward iterator from 7 to 8") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(5)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{4}, "reverse iterator from 5 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{5, 4}, "reverse iterator from 6 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{5, 4}, "reverse iterator from 7 (ex) to 4") + + ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(6)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{5}, "reverse iterator from 6 (ex) to 5") + + ritr, err = db.ReverseIterator(int642Bytes(5), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{5}, "reverse iterator from 7 (ex) to 5") + + ritr, err = db.ReverseIterator(int642Bytes(6), int642Bytes(7)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64(nil), "reverse iterator from 7 (ex) to 6") + + ritr, err = db.ReverseIterator(int642Bytes(10), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64(nil), "reverse iterator to 10") + + ritr, err = db.ReverseIterator(int642Bytes(6), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7}, "reverse iterator to 6") + + ritr, err = db.ReverseIterator(int642Bytes(5), nil) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{9, 8, 7, 5}, "reverse iterator to 5") + + ritr, err = db.ReverseIterator(int642Bytes(8), int642Bytes(9)) + require.NoError(t, err) + verifyIterator(t, ritr, []int64{8}, "reverse iterator from 9 (ex) to 8") + + ritr, err = db.ReverseIterator(int642Bytes(2), int642Bytes(4)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64{3, 2}, "reverse iterator from 4 (ex) to 2") + + ritr, err = db.ReverseIterator(int642Bytes(4), int642Bytes(2)) + require.NoError(t, err) + verifyIterator(t, ritr, + []int64(nil), "reverse iterator from 2 (ex) to 4") + + // Ensure that the iterators don't panic with an empty database. + db2 := NewBTree() + + itr, err = db2.Iterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, itr, nil, "forward iterator with empty db") + + ritr, err = db2.ReverseIterator(nil, nil) + require.NoError(t, err) + verifyIterator(t, ritr, nil, "reverse iterator with empty db") +} + +func verifyIterator(t *testing.T, itr types.Iterator, expected []int64, msg string) { + t.Helper() + i := 0 + for itr.Valid() { + key := itr.Key() + require.Equal(t, expected[i], bytes2Int64(key), "iterator: %d mismatches", i) + itr.Next() + i++ + } + require.Equal(t, i, len(expected), "expected to have fully iterated over all the elements in iter") + require.NoError(t, itr.Close()) +} + +func int642Bytes(i int64) []byte { + return types.Uint64ToBigEndian(uint64(i)) +} + +func bytes2Int64(buf []byte) int64 { + return int64(types.BigEndianToUint64(buf)) +} diff --git a/iavlx/internal/memiterator.go b/iavlx/internal/memiterator.go new file mode 100644 index 000000000000..ab7e6e13a6ba --- /dev/null +++ b/iavlx/internal/memiterator.go @@ -0,0 +1,120 @@ +package internal + +import ( + "bytes" + "errors" + + "github.com/tidwall/btree" + + "cosmossdk.io/store/types" +) + +var _ types.Iterator = (*memIterator)(nil) + +// memIterator iterates over iterKVCache items. +// if value is nil, means it was deleted. +// Implements Iterator. +type memIterator struct { + iter btree.IterG[item] + + start []byte + end []byte + ascending bool + valid bool +} + +func newMemIterator(start, end []byte, items BTree, ascending bool) *memIterator { + iter := items.tree.Iter() + var valid bool + if ascending { + if start != nil { + valid = iter.Seek(newItem(start, nil, false)) + } else { + valid = iter.First() + } + } else { + if end != nil { + valid = iter.Seek(newItem(end, nil, false)) + if !valid { + valid = iter.Last() + } else { + // end is exclusive + valid = iter.Prev() + } + } else { + valid = iter.Last() + } + } + + mi := &memIterator{ + iter: iter, + start: start, + end: end, + ascending: ascending, + valid: valid, + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } + + return mi +} + +func (mi *memIterator) Domain() (start, end []byte) { + return mi.start, mi.end +} + +func (mi *memIterator) Close() error { + mi.iter.Release() + return nil +} + +func (mi *memIterator) Error() error { + if !mi.Valid() { + return errors.New("invalid memIterator") + } + return nil +} + +func (mi *memIterator) Valid() bool { + return mi.valid +} + +func (mi *memIterator) Next() { + mi.assertValid() + + if mi.ascending { + mi.valid = mi.iter.Next() + } else { + mi.valid = mi.iter.Prev() + } + + if mi.valid { + mi.valid = mi.keyInRange(mi.Key()) + } +} + +func (mi *memIterator) keyInRange(key []byte) bool { + if mi.ascending && mi.end != nil && bytes.Compare(key, mi.end) >= 0 { + return false + } + if !mi.ascending && mi.start != nil && bytes.Compare(key, mi.start) < 0 { + return false + } + return true +} + +func (mi *memIterator) Key() []byte { + return mi.iter.Item().key +} + +func (mi *memIterator) Value() []byte { + return mi.iter.Item().value +} + +func (mi *memIterator) assertValid() { + if err := mi.Error(); err != nil { + panic(err) + } +} diff --git a/iavlx/internal/mergeiterator.go b/iavlx/internal/mergeiterator.go new file mode 100644 index 000000000000..15b215841931 --- /dev/null +++ b/iavlx/internal/mergeiterator.go @@ -0,0 +1,235 @@ +package internal + +import ( + "bytes" + "errors" + + "cosmossdk.io/store/types" +) + +// cacheMergeIterator merges a parent Iterator and a cache Iterator. +// The cache iterator may return nil keys to signal that an item +// had been deleted (but not deleted in the parent). +// If the cache iterator has the same key as the parent, the +// cache shadows (overrides) the parent. +// +// TODO: Optimize by memoizing. +type cacheMergeIterator struct { + parent types.Iterator + cache types.Iterator + ascending bool + + valid bool +} + +var _ types.Iterator = (*cacheMergeIterator)(nil) + +func NewCacheMergeIterator(parent, cache types.Iterator, ascending bool) types.Iterator { + iter := &cacheMergeIterator{ + parent: parent, + cache: cache, + ascending: ascending, + } + + iter.valid = iter.skipUntilExistsOrInvalid() + return iter +} + +// Domain implements Iterator. +// Returns parent domain because cache and parent domains are the same. +func (iter *cacheMergeIterator) Domain() (start, end []byte) { + return iter.parent.Domain() +} + +// Valid implements Iterator. +func (iter *cacheMergeIterator) Valid() bool { + return iter.valid +} + +// Next implements Iterator +func (iter *cacheMergeIterator) Next() { + iter.assertValid() + + switch { + case !iter.parent.Valid(): + // If parent is invalid, get the next cache item. + iter.cache.Next() + case !iter.cache.Valid(): + // If cache is invalid, get the next parent item. + iter.parent.Next() + default: + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + switch iter.compare(keyP, keyC) { + case -1: // parent < cache + iter.parent.Next() + case 0: // parent == cache + iter.parent.Next() + iter.cache.Next() + case 1: // parent > cache + iter.cache.Next() + } + } + iter.valid = iter.skipUntilExistsOrInvalid() +} + +// Key implements Iterator +func (iter *cacheMergeIterator) Key() []byte { + iter.assertValid() + + // If parent is invalid, get the cache key. + if !iter.parent.Valid() { + return iter.cache.Key() + } + + // If cache is invalid, get the parent key. + if !iter.cache.Valid() { + return iter.parent.Key() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return keyP + case 0: // parent == cache + return keyP + case 1: // parent > cache + return keyC + default: + panic("invalid compare result") + } +} + +// Value implements Iterator +func (iter *cacheMergeIterator) Value() []byte { + iter.assertValid() + + // If parent is invalid, get the cache value. + if !iter.parent.Valid() { + return iter.cache.Value() + } + + // If cache is invalid, get the parent value. + if !iter.cache.Valid() { + return iter.parent.Value() + } + + // Both are valid. Compare keys. + keyP, keyC := iter.parent.Key(), iter.cache.Key() + + cmp := iter.compare(keyP, keyC) + switch cmp { + case -1: // parent < cache + return iter.parent.Value() + case 0: // parent == cache + return iter.cache.Value() + case 1: // parent > cache + return iter.cache.Value() + default: + panic("invalid comparison result") + } +} + +// Close implements Iterator +func (iter *cacheMergeIterator) Close() error { + err1 := iter.cache.Close() + if err := iter.parent.Close(); err != nil { + return err + } + + return err1 +} + +// Error returns an error if the cacheMergeIterator is invalid defined by the +// Valid method. +func (iter *cacheMergeIterator) Error() error { + if !iter.Valid() { + return errors.New("invalid cacheMergeIterator") + } + + return nil +} + +// assertValid checks if not valid, panics. +// NOTE: May have side-effect of iterating over cache. +func (iter *cacheMergeIterator) assertValid() { + if err := iter.Error(); err != nil { + panic(err) + } +} + +// compare is like bytes.Compare but opposite if not ascending. +func (iter *cacheMergeIterator) compare(a, b []byte) int { + if iter.ascending { + return bytes.Compare(a, b) + } + + return bytes.Compare(a, b) * -1 +} + +// skipCacheDeletes skips all delete-items from the cache w/ `key < until`. After this function, +// current cache item is a non-delete-item, or `until <= key`. +// If the current cache item is not a delete item, does nothing. +// If `until` is nil, there is no limit, and cache may end up invalid. +// CONTRACT: cache is valid. +func (iter *cacheMergeIterator) skipCacheDeletes(until []byte) { + for iter.cache.Valid() && + iter.cache.Value() == nil && + (until == nil || iter.compare(iter.cache.Key(), until) < 0) { + iter.cache.Next() + } +} + +// skipUntilExistsOrInvalid fast forwards cache (or parent+cache in case of deleted items) until current +// item exists, or until iterator becomes invalid. +// Returns whether the iterator is valid. +func (iter *cacheMergeIterator) skipUntilExistsOrInvalid() bool { + for { + // If parent is invalid, fast-forward cache. + if !iter.parent.Valid() { + iter.skipCacheDeletes(nil) + return iter.cache.Valid() + } + // Parent is valid. + + if !iter.cache.Valid() { + return true + } + // Parent is valid, cache is valid. + + // Compare parent and cache. + keyP := iter.parent.Key() + keyC := iter.cache.Key() + + switch iter.compare(keyP, keyC) { + case -1: // parent < cache. + return true + + case 0: // parent == cache. + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.parent.Next() + iter.cache.Next() + + continue + } + // Cache is not a delete. + + return true // cache exists. + case 1: // cache < parent + // Skip over if cache item is a delete. + valueC := iter.cache.Value() + if valueC == nil { + iter.skipCacheDeletes(keyP) + continue + } + // Cache is not a delete. + + return true // cache exists. + } + } +} diff --git a/store/iavlx/iterator.go b/iavlx/iterator.go similarity index 100% rename from store/iavlx/iterator.go rename to iavlx/iterator.go diff --git a/store/iavlx/kvlog.go b/iavlx/kvlog.go similarity index 100% rename from store/iavlx/kvlog.go rename to iavlx/kvlog.go diff --git a/store/iavlx/kvlog_writer.go b/iavlx/kvlog_writer.go similarity index 100% rename from store/iavlx/kvlog_writer.go rename to iavlx/kvlog_writer.go diff --git a/store/iavlx/leaf_layout.go b/iavlx/leaf_layout.go similarity index 100% rename from store/iavlx/leaf_layout.go rename to iavlx/leaf_layout.go diff --git a/store/iavlx/leaf_persisted.go b/iavlx/leaf_persisted.go similarity index 100% rename from store/iavlx/leaf_persisted.go rename to iavlx/leaf_persisted.go diff --git a/store/iavlx/mem_node.go b/iavlx/mem_node.go similarity index 100% rename from store/iavlx/mem_node.go rename to iavlx/mem_node.go diff --git a/store/iavlx/mmap.go b/iavlx/mmap.go similarity index 100% rename from store/iavlx/mmap.go rename to iavlx/mmap.go diff --git a/store/iavlx/multi_tree.go b/iavlx/multi_tree.go similarity index 100% rename from store/iavlx/multi_tree.go rename to iavlx/multi_tree.go diff --git a/store/iavlx/node.go b/iavlx/node.go similarity index 100% rename from store/iavlx/node.go rename to iavlx/node.go diff --git a/store/iavlx/node_hash.go b/iavlx/node_hash.go similarity index 100% rename from store/iavlx/node_hash.go rename to iavlx/node_hash.go diff --git a/store/iavlx/node_id.go b/iavlx/node_id.go similarity index 100% rename from store/iavlx/node_id.go rename to iavlx/node_id.go diff --git a/store/iavlx/node_pointer.go b/iavlx/node_pointer.go similarity index 100% rename from store/iavlx/node_pointer.go rename to iavlx/node_pointer.go diff --git a/store/iavlx/node_update.go b/iavlx/node_update.go similarity index 100% rename from store/iavlx/node_update.go rename to iavlx/node_update.go diff --git a/store/iavlx/non_blocking_queue.go b/iavlx/non_blocking_queue.go similarity index 100% rename from store/iavlx/non_blocking_queue.go rename to iavlx/non_blocking_queue.go diff --git a/store/iavlx/options.go b/iavlx/options.go similarity index 100% rename from store/iavlx/options.go rename to iavlx/options.go diff --git a/store/iavlx/reader.go b/iavlx/reader.go similarity index 100% rename from store/iavlx/reader.go rename to iavlx/reader.go diff --git a/store/iavlx/tree_store.go b/iavlx/tree_store.go similarity index 100% rename from store/iavlx/tree_store.go rename to iavlx/tree_store.go diff --git a/store/iavlx/tree_test.go b/iavlx/tree_test.go similarity index 95% rename from store/iavlx/tree_test.go rename to iavlx/tree_test.go index 0bb57e2df022..6d5e3428c736 100644 --- a/store/iavlx/tree_test.go +++ b/iavlx/tree_test.go @@ -15,6 +15,8 @@ import ( corestore "cosmossdk.io/core/store" sdklog "cosmossdk.io/log" + + "cosmossdk.io/store/types" ) func TestBasicTest(t *testing.T) { @@ -23,7 +25,7 @@ func TestBasicTest(t *testing.T) { defer os.RemoveAll(dir) commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger()) require.NoError(t, err) - tree := commitTree.CacheWrap().(*Tree) + tree := commitTree.CacheWrap().(types.CacheKVStore) tree.Set([]byte{0}, []byte{1}) // renderTree(t, tree) @@ -76,7 +78,7 @@ func TestBasicTest(t *testing.T) { func renderTree(t interface { require.TestingT Logf(format string, args ...any) -}, tree *Tree, +}, tree *ImmutableTree, ) { graph := &bytes.Buffer{} require.NoError(t, RenderDotGraph(graph, tree.root)) @@ -179,7 +181,7 @@ func (s *SimMachine) set(t *rapid.T) { // set in both trees updated, errV1 := s.treeV1.Set(key, value) require.NoError(t, errV1, "failed to set key in V1 tree") - branch := s.treeV2.CacheWrap().(*Tree) + branch := s.treeV2.CacheWrap().(types.CacheKVStore) branch.Set(key, value) branch.Write() // require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees") @@ -198,7 +200,7 @@ func (s *SimMachine) get(t *rapid.T) { key := s.selectKey(t) valueV1, errV1 := s.treeV1.Get(key) require.NoError(t, errV1, "failed to get key from V1 tree") - valueV2 := s.treeV2.CacheWrap().(*Tree).Get(key) + valueV2 := s.treeV2.CacheWrap().(types.CacheKVStore).Get(key) require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") expectedValue, found := s.existingKeys[string(key)] if found { @@ -213,7 +215,7 @@ func (s *SimMachine) selectKey(t *rapid.T) []byte { return []byte(rapid.SampledFrom(maps.Keys(s.existingKeys)).Draw(t, "key")) } else { // TODO consider testing longer keys - return rapid.SliceOfN(rapid.Byte(), 0, 10).Draw(t, "key") + return rapid.SliceOfN(rapid.Byte(), 1, 10).Draw(t, "key") } } @@ -224,7 +226,7 @@ func (s *SimMachine) delete(t *rapid.T) { // delete in both trees _, removedV1, errV1 := s.treeV1.Remove(key) require.NoError(t, errV1, "failed to remove key from V1 tree") - branch := s.treeV2.CacheWrap().(*Tree) + branch := s.treeV2.CacheWrap().(types.CacheKVStore) branch.Delete(key) branch.Write() // require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees") @@ -289,7 +291,7 @@ func (s *SimMachine) debugDump(t *rapid.T) { iavl.WriteDOTGraph(graph1, s.treeV1.ImmutableTree, nil) t.Logf("V1 tree:\n%s", graph1.String()) // renderTree(t, s.treeV2.Branch()) - iter2 := s.treeV2.CacheWrap().(*Tree).Iterator(nil, nil) + iter2 := s.treeV2.CacheWrap().(types.CacheKVStore).Iterator(nil, nil) s.debugDumpTree(t, iter2) } @@ -330,7 +332,7 @@ func (s *SimMachine) debugDumpTree(t *rapid.T, iter corestore.Iterator) { func (s *SimMachine) compareIterators(t *rapid.T, start, end []byte, ascending bool) { iter1, err1 := s.treeV1.Iterator(start, end, ascending) require.NoError(t, err1, "failed to create iterator for V1 tree") - iter2 := s.treeV2.CacheWrap().(*Tree).Iterator(start, end) + iter2 := s.treeV2.CacheWrap().(types.CacheKVStore).Iterator(start, end) compareIteratorsAtVersion(t, iter1, iter2) } diff --git a/store/iavlx/update.go b/iavlx/update.go similarity index 100% rename from store/iavlx/update.go rename to iavlx/update.go diff --git a/store/iavlx/verify.go b/iavlx/verify.go similarity index 100% rename from store/iavlx/verify.go rename to iavlx/verify.go diff --git a/store/iavlx/version_info.go b/iavlx/version_info.go similarity index 100% rename from store/iavlx/version_info.go rename to iavlx/version_info.go diff --git a/store/iavlx/writer.go b/iavlx/writer.go similarity index 100% rename from store/iavlx/writer.go rename to iavlx/writer.go diff --git a/store/iavlx/tree.go b/store/iavlx/tree.go deleted file mode 100644 index 8dc3c0f6118e..000000000000 --- a/store/iavlx/tree.go +++ /dev/null @@ -1,144 +0,0 @@ -package iavlx - -import ( - io "io" - - corestore "cosmossdk.io/core/store" - storetypes "cosmossdk.io/store/types" -) - -type Tree struct { - parent parentTree - origRoot *NodePointer - root *NodePointer - updateBatch KVUpdateBatch - zeroCopy bool -} - -type parentTree interface { - getRoot() *NodePointer - applyChangesToParent(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error -} - -func NewTree(parent parentTree, stagedVersion uint32, zeroCopy bool) *Tree { - root := parent.getRoot() - return &Tree{ - parent: parent, - root: root, - origRoot: root, - updateBatch: KVUpdateBatch{Version: stagedVersion}, - zeroCopy: zeroCopy, - } -} - -func (tree *Tree) getRoot() *NodePointer { - return tree.root -} - -func (tree *Tree) applyChangesToParent(origRoot, newRoot *NodePointer, updateBatch KVUpdateBatch) error { - if tree.root != origRoot { - panic("cannot apply changes: root has changed") - } - tree.root = newRoot - tree.updateBatch.Updates = append(tree.updateBatch.Updates, updateBatch.Updates...) - tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, updateBatch.Orphans...) - return nil -} - -func (tree *Tree) GetStoreType() storetypes.StoreType { - return storetypes.StoreTypeIAVL -} - -func (tree *Tree) CacheWrap() storetypes.CacheWrap { - return NewTree(tree, tree.updateBatch.Version, tree.zeroCopy) -} - -func (tree *Tree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - // TODO support tracing - return tree.CacheWrap() -} - -func (tree *Tree) Write() { - if tree.parent == nil { - panic("cannot write: tree is immutable") - } - err := tree.parent.applyChangesToParent(tree.origRoot, tree.root, tree.updateBatch) - if err != nil { - panic(err) - } - tree.updateBatch.Updates = nil - tree.updateBatch.Orphans = nil - tree.root = tree.parent.getRoot() - tree.origRoot = tree.root -} - -func (tree *Tree) Get(key []byte) []byte { - if tree.root == nil { - return nil - } - - root, err := tree.root.Resolve() - if err != nil { - panic(err) - } - - value, _, err := root.Get(key) - if err != nil { - panic(err) - } - - return value -} - -func (tree *Tree) Set(key, value []byte) { - leafNode := &MemNode{ - height: 0, - size: 1, - version: tree.updateBatch.Version, - key: key, - value: value, - } - ctx := &MutationContext{Version: tree.updateBatch.Version} - newRoot, _, err := setRecursive(tree.root, leafNode, ctx) - if err != nil { - panic(err) - } - - tree.root = newRoot - tree.updateBatch.Updates = append(tree.updateBatch.Updates, KVUpdate{ - SetNode: leafNode, - }) - tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) -} - -func (tree *Tree) Delete(key []byte) { - ctx := &MutationContext{Version: tree.updateBatch.Version} - _, newRoot, _, err := removeRecursive(tree.root, key, ctx) - if err != nil { - panic(err) - } - tree.root = newRoot - tree.updateBatch.Updates = append(tree.updateBatch.Updates, KVUpdate{ - DeleteKey: key, - }) - tree.updateBatch.Orphans = append(tree.updateBatch.Orphans, ctx.Orphans) -} - -func (tree *Tree) Has(key []byte) bool { - // TODO optimize this if possible - val := tree.Get(key) - return val != nil -} - -func (tree *Tree) Iterator(start, end []byte) corestore.Iterator { - return NewIterator(start, end, true, tree.root, tree.zeroCopy) -} - -func (tree *Tree) ReverseIterator(start, end []byte) corestore.Iterator { - return NewIterator(start, end, false, tree.root, tree.zeroCopy) -} - -var ( - _ storetypes.CacheKVStore = (*Tree)(nil) - _ parentTree = (*Tree)(nil) -) From 582bee03fd19bcedb671b1722ab94fb1b673444d Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 19:08:44 -0400 Subject: [PATCH 33/87] fix import issue --- server/util.go | 2 +- simapp/app_di.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/util.go b/server/util.go index f5b9a3effa60..6c99aacf80b4 100644 --- a/server/util.go +++ b/server/util.go @@ -34,7 +34,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client/flags" - iavlx "github.com/cosmos/cosmos-sdk/iavl" + "github.com/cosmos/cosmos-sdk/iavlx" "github.com/cosmos/cosmos-sdk/server/config" "github.com/cosmos/cosmos-sdk/server/types" sdk "github.com/cosmos/cosmos-sdk/types" diff --git a/simapp/app_di.go b/simapp/app_di.go index b3213107a6a6..38020e0d3369 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -21,7 +21,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - iavlx "github.com/cosmos/cosmos-sdk/iavl" + "github.com/cosmos/cosmos-sdk/iavlx" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/api" From 67a55a315196c197d75ec99a8f402b873f02591a Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 27 Oct 2025 20:42:49 -0400 Subject: [PATCH 34/87] move wal write timing --- iavlx/commit_tree.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go index f2ff1548e5f4..3ca2f55664c2 100644 --- a/iavlx/commit_tree.go +++ b/iavlx/commit_tree.go @@ -201,6 +201,12 @@ func (c *CommitTree) Set(key, value []byte) { key: key, value: value, } + + if c.writeWal { + // start writing this to the WAL asynchronously before we even mutate the tree + c.walQueue.Send([]KVUpdate{{SetNode: leafNode}}) + } + ctx := &MutationContext{Version: stagedVersion} newRoot, _, err := setRecursive(c.root, leafNode, ctx) if err != nil { @@ -208,7 +214,7 @@ func (c *CommitTree) Set(key, value []byte) { } c.root = newRoot - c.applyUpdates([]KVUpdate{{SetNode: leafNode}}, ctx.Orphans) + c.pendingOrphans = append(c.pendingOrphans, ctx.Orphans) } func (c *CommitTree) Delete(key []byte) { @@ -217,21 +223,18 @@ func (c *CommitTree) Delete(key []byte) { c.writeMutex.Lock() defer c.writeMutex.Unlock() + if c.writeWal { + // start writing this to the WAL asynchronously before we even mutate the tree + c.walQueue.Send([]KVUpdate{{DeleteKey: key}}) + } + ctx := &MutationContext{Version: c.stagedVersion()} _, newRoot, _, err := removeRecursive(c.root, key, ctx) if err != nil { panic(err) } c.root = newRoot - c.applyUpdates([]KVUpdate{{DeleteKey: key}}, ctx.Orphans) -} - -func (c *CommitTree) applyUpdates(updates []KVUpdate, orphans []NodeID) { - c.pendingOrphans = append(c.pendingOrphans, orphans) - - if c.writeWal { - c.walQueue.Send(updates) - } + c.pendingOrphans = append(c.pendingOrphans, ctx.Orphans) } func (c *CommitTree) Iterator(start, end []byte) storetypes.Iterator { From 1cf5e6c79fa2458a889aa64f055ca726c8961b8a Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 29 Oct 2025 15:44:07 -0400 Subject: [PATCH 35/87] feat: add tracing --- log/tracer.go | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 log/tracer.go diff --git a/log/tracer.go b/log/tracer.go new file mode 100644 index 000000000000..ed247b73ed6a --- /dev/null +++ b/log/tracer.go @@ -0,0 +1,120 @@ +package log + +import "context" + +// Tracer is an interface for creating and managing spans. +// It may be backed by open telemetry or other tracing libraries, +// Spans may also be used for collecting timing metrics. +// It embeds the Logger interface. Log events may be associated with spans. +type Tracer interface { + Logger + + // StartSpan starts a new span with the given operation name and key-value pair attributes. + // If there is a parent span, the new span will be a child of that span. + // It is recommended to use a defer statement to end the span like this: + // span := tracer.StartSpan("my-span") + // defer span.End() + StartSpan(operation string, kvs ...any) Span + + // StartSpanContext attempts to retrieve an existing tracer from the context and then starts a new span + // as a child of that span. + // If no tracer is found, it returns a new span that is a child of this tracer instance. + // This is useful if a span may have been set in the context, but we are not sure. + // The function also returns a context with the span added to it. + StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, Span) + + // RootTracer returns a root-level tracer that is not part of any existing span. + // Use this when starting async work to ensure its spans are not timed as part of the current span. + // Example usage: + // rootTracer := tracer.RootTracer() + // go func() { + // span := rootTracer.StartSpan("my-go-routine") + // defer span.End() + // doSomething() + // }() + RootTracer() Tracer +} + +// Span is an interface for managing spans and creating nested spans via the embedded Tracer interface. +type Span interface { + // Tracer is embedded to allow for the creation of nested spans. + Tracer + + // SetAttrs sets additional key-value attributes on the span. + SetAttrs(kvs ...any) + + // SetErr records an optional error on the span and optionally adds additional key-value pair attributes. + // It returns the error value unchanged, allowing use in return statements. + // If err is nil, the span is marked as successful. + // If err is not nil, the span is marked as failed. + // This does NOT end the span, you must still call End. + // Example usage: + // span := tracer.StartSpan("my-span") + // defer span.End() + // err := doSomething() + // return span.SetErr(err, "additional", "info") // okay to call with a nil error + SetErr(err error, kvs ...any) error + + // End marks the end of a span and is designed to be used in a defer statement right after the span is created. + // Calling End on a span that has already ended is a no-op. + // Example usage: + // span := tracer.StartSpan("my-span") + // defer span.End() + End() +} + +type traceContextKeyType string + +const traceContextKey traceContextKeyType = "trace-context" + +func ContextWithTracer(ctx context.Context, tracer Tracer) context.Context { + return context.WithValue(ctx, traceContextKey, tracer) +} + +// TracerFromContext returns the Tracer from the context. +// If no Tracer is found, it returns a no-op Tracer that is safe to use. +func TracerFromContext(ctx context.Context) Tracer { + if tracer, ok := ctx.Value(traceContextKey).(Tracer); ok { + return tracer + } + return NewNopTracer() +} + +// NewNopTracer returns a Tracer that does nothing. +func NewNopTracer() Tracer { + return nopTracer{} +} + +type nopTracer struct { + nopLogger +} + +func (n nopTracer) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, Span) { + if tracer, ok := ctx.Value(traceContextKey).(Tracer); ok { + span := tracer.StartSpan(operation, kvs...) + return ContextWithTracer(ctx, span), span + } + // no tracer found in context, create a new span, no need to add to context + return ctx, nopSpan{} +} + +func (n nopTracer) StartSpan(string, ...any) Span { + return nopSpan{} +} + +func (n nopTracer) RootTracer() Tracer { + return n +} + +type nopSpan struct { + nopTracer +} + +func (n nopSpan) SetAttrs(...any) {} + +func (n nopSpan) SetErr(err error, _ ...any) error { return err } + +func (n nopSpan) End() {} + +var _ Tracer = nopTracer{} +var _ Span = nopSpan{} From a207f94910406dbe944d4e6a74c47b1a68f10840 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 29 Oct 2025 16:54:12 -0400 Subject: [PATCH 36/87] work on otel tracer impl --- log/tracer.go | 39 +++-------- telemetry/otel.go | 168 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 telemetry/otel.go diff --git a/log/tracer.go b/log/tracer.go index ed247b73ed6a..cfb99eafdb57 100644 --- a/log/tracer.go +++ b/log/tracer.go @@ -23,16 +23,15 @@ type Tracer interface { // The function also returns a context with the span added to it. StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, Span) - // RootTracer returns a root-level tracer that is not part of any existing span. + // StartRootSpan returns a root-level span that doesn't have a parent. // Use this when starting async work to ensure its spans are not timed as part of the current span. // Example usage: - // rootTracer := tracer.RootTracer() // go func() { - // span := rootTracer.StartSpan("my-go-routine") + // ctx, span := outerSpan.StartRootSpan(ctx, "my-go-routine") // defer span.End() // doSomething() // }() - RootTracer() Tracer + StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, Span) } // Span is an interface for managing spans and creating nested spans via the embedded Tracer interface. @@ -63,23 +62,6 @@ type Span interface { End() } -type traceContextKeyType string - -const traceContextKey traceContextKeyType = "trace-context" - -func ContextWithTracer(ctx context.Context, tracer Tracer) context.Context { - return context.WithValue(ctx, traceContextKey, tracer) -} - -// TracerFromContext returns the Tracer from the context. -// If no Tracer is found, it returns a no-op Tracer that is safe to use. -func TracerFromContext(ctx context.Context) Tracer { - if tracer, ok := ctx.Value(traceContextKey).(Tracer); ok { - return tracer - } - return NewNopTracer() -} - // NewNopTracer returns a Tracer that does nothing. func NewNopTracer() Tracer { return nopTracer{} @@ -89,21 +71,16 @@ type nopTracer struct { nopLogger } -func (n nopTracer) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, Span) { - if tracer, ok := ctx.Value(traceContextKey).(Tracer); ok { - span := tracer.StartSpan(operation, kvs...) - return ContextWithTracer(ctx, span), span - } - // no tracer found in context, create a new span, no need to add to context +func (n nopTracer) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, Span) { return ctx, nopSpan{} } -func (n nopTracer) StartSpan(string, ...any) Span { - return nopSpan{} +func (n nopTracer) StartSpanContext(ctx context.Context, _ string, _ ...any) (context.Context, Span) { + return ctx, nopSpan{} } -func (n nopTracer) RootTracer() Tracer { - return n +func (n nopTracer) StartSpan(string, ...any) Span { + return nopSpan{} } type nopSpan struct { diff --git a/telemetry/otel.go b/telemetry/otel.go new file mode 100644 index 000000000000..b3172d39abb3 --- /dev/null +++ b/telemetry/otel.go @@ -0,0 +1,168 @@ +package telemetry + +import ( + "context" + "fmt" + + otelattr "go.opentelemetry.io/otel/attribute" + otelcodes "go.opentelemetry.io/otel/codes" + oteltrace "go.opentelemetry.io/otel/trace" + + "cosmossdk.io/log" +) + +type OtelSpan struct { + tracer oteltrace.Tracer + ctx context.Context + span oteltrace.Span + persistentAttrs []otelattr.KeyValue +} + +func NewOtelSpan(tracer oteltrace.Tracer, ctx context.Context, operation string, kvs ...any) (context.Context, *OtelSpan) { + ctx, span := tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...)) + return ctx, &OtelSpan{ + tracer: tracer, + ctx: ctx, + span: span, + } +} + +func (o *OtelSpan) addEvent(level, msg string, keyVals ...any) { + o.span.AddEvent(msg, + oteltrace.WithAttributes(o.persistentAttrs...), + oteltrace.WithAttributes(toKVs(keyVals...)...), + oteltrace.WithAttributes(otelattr.String("level", level)), + ) +} + +func (o *OtelSpan) Info(msg string, keyVals ...any) { + o.addEvent("info", msg, keyVals...) +} + +func (o *OtelSpan) Warn(msg string, keyVals ...any) { + o.addEvent("warn", msg, keyVals...) +} + +func (o *OtelSpan) Error(msg string, keyVals ...any) { + o.addEvent("error", msg, keyVals...) +} + +func (o *OtelSpan) Debug(msg string, keyVals ...any) { + o.addEvent("debug", msg, keyVals...) +} + +func (o *OtelSpan) With(keyVals ...any) log.Logger { + attrs := toKVs(keyVals...) + persistentAttrs := make([]otelattr.KeyValue, 0, len(o.persistentAttrs)+len(attrs)) + persistentAttrs = append(persistentAttrs, o.persistentAttrs...) + persistentAttrs = append(persistentAttrs, attrs...) + return &OtelSpan{ + tracer: o.tracer, + ctx: o.ctx, + span: o.span, + persistentAttrs: persistentAttrs, + } +} + +func (o *OtelSpan) Impl() any { + return o.span +} + +func (o *OtelSpan) startSpan(ctx context.Context, operation string, kvs []any, opts ...oteltrace.SpanStartOption) *OtelSpan { + if len(o.persistentAttrs) > 0 { + opts = append(opts, oteltrace.WithAttributes(o.persistentAttrs...)) + } + opts = append(opts, oteltrace.WithAttributes(toKVs(kvs...)...)) + ctx, span := o.tracer.Start(ctx, operation, opts...) + return &OtelSpan{ + tracer: o.tracer, + ctx: ctx, + span: span, + persistentAttrs: o.persistentAttrs, + } +} + +func (o *OtelSpan) StartSpan(operation string, kvs ...any) log.Span { + return o.startSpan(o.ctx, operation, kvs) +} + +func (o *OtelSpan) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + if !oteltrace.SpanContextFromContext(ctx).IsValid() { + // if we don't have a valid span in the context, use the one from the tracer + ctx = o.ctx + } + span := o.startSpan(ctx, operation, kvs) + return span.ctx, span +} + +func (o *OtelSpan) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + span := o.startSpan(ctx, operation, kvs, oteltrace.WithNewRoot()) + return span.ctx, span +} + +func (o *OtelSpan) SetAttrs(kvs ...any) { + o.span.SetAttributes(toKVs(kvs...)...) +} + +func (o *OtelSpan) SetErr(err error, kvs ...any) error { + if err == nil { + o.span.SetStatus(otelcodes.Ok, "OK") + } else { + o.span.RecordError(err) + o.span.SetStatus(otelcodes.Error, err.Error()) + } + if len(kvs) > 0 { + o.span.SetAttributes(toKVs(kvs...)...) + } + return err +} + +func (o *OtelSpan) End() { + o.span.End() +} + +var _ log.Span = (*OtelSpan)(nil) + +func toKVs(kvs ...any) []otelattr.KeyValue { + if len(kvs)%2 != 0 { + panic(fmt.Sprintf("kvs must have even length, got %d", len(kvs))) + } + res := make([]otelattr.KeyValue, 0, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + key, ok := kvs[i].(string) + if !ok { + panic("key must be string") + } + res = append(res, otelattr.KeyValue{ + Key: otelattr.Key(key), + Value: toValue(kvs[i+1]), + }) + } + return res +} + +func toValue(value any) otelattr.Value { + switch v := value.(type) { + case bool: + return otelattr.BoolValue(v) + case string: + return otelattr.StringValue(v) + case int64: + return otelattr.Int64Value(v) + case int: + return otelattr.IntValue(v) + case float64: + return otelattr.Float64Value(v) + case []string: + return otelattr.StringSliceValue(v) + case []int64: + return otelattr.Int64SliceValue(v) + case []int: + return otelattr.IntSliceValue(v) + case []float64: + return otelattr.Float64SliceValue(v) + default: + return otelattr.StringValue(fmt.Sprintf("%+v", value)) + } + +} From ee0bfb37891f12fb2483ac22063a7429d51a99c3 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 29 Oct 2025 16:54:59 -0400 Subject: [PATCH 37/87] add basic baseapp tracing --- baseapp/abci.go | 41 ++++++++++++++++++++++++++---- baseapp/baseapp.go | 49 +++++++++++++++++++++++++++++------- baseapp/genesis.go | 2 +- baseapp/test_helpers.go | 9 ++++--- baseapp/txnrunner/default.go | 7 ++++-- blockstm/executor.go | 4 ++- blockstm/txnrunner.go | 5 ++-- blockstm/types.go | 4 ++- client/v2/go.mod | 2 +- client/v2/go.sum | 4 +-- go.mod | 4 ++- go.sum | 6 ++--- simapp/go.mod | 6 ++++- simapp/go.sum | 4 +-- systemtests/go.mod | 2 +- systemtests/go.sum | 4 +-- tests/go.mod | 2 +- tests/go.sum | 4 +-- tests/systemtests/go.mod | 2 +- tests/systemtests/go.sum | 4 +-- types/abci.go | 4 ++- 21 files changed, 123 insertions(+), 46 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index f7ede1c840f6..dd4999af0334 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -39,6 +39,9 @@ const ( ) func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + span := app.tracer.StartSpan("InitChain") + defer span.End() + if req.ChainId != app.chainID { return nil, fmt.Errorf("invalid chain-id on InitChain; expected: %s, got: %s", app.chainID, req.ChainId) } @@ -153,6 +156,9 @@ func (app *BaseApp) Info(_ *abci.RequestInfo) (*abci.ResponseInfo, error) { // Query implements the ABCI interface. It delegates to CommitMultiStore if it // implements Queryable. func (app *BaseApp) Query(_ context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) { + span := app.tracer.StartSpan("Query") + defer span.End() + // add panic recovery for all queries // // Ref: https://github.com/cosmos/cosmos-sdk/pull/8039 @@ -342,6 +348,9 @@ func (app *BaseApp) ApplySnapshotChunk(req *abci.RequestApplySnapshotChunk) (*ab // will contain relevant error information. Regardless of tx execution outcome, // the ResponseCheckTx will contain the relevant gas execution context. func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { + span := app.tracer.StartSpan("CheckTx") + defer span.End() + var mode sdk.ExecMode switch req.Type { @@ -356,7 +365,7 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er } if app.abciHandlers.CheckTxHandler == nil { - gasInfo, result, anteEvents, err := app.RunTx(mode, req.Tx, nil, -1, nil, nil) + gasInfo, result, anteEvents, err := app.RunTx(mode, req.Tx, nil, -1, nil, nil, span) if err != nil { return sdkerrors.ResponseCheckTxWithEvents(err, gasInfo.GasWanted, gasInfo.GasUsed, anteEvents, app.trace), nil } @@ -372,7 +381,7 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er // Create wrapper to avoid users overriding the execution mode runTx := func(txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { - return app.RunTx(mode, txBytes, tx, -1, nil, nil) + return app.RunTx(mode, txBytes, tx, -1, nil, nil, span) } return app.abciHandlers.CheckTxHandler(runTx, req) @@ -392,6 +401,9 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) { + span := app.tracer.StartSpan("PrepareProposal") + defer span.End() + if app.abciHandlers.PrepareProposalHandler == nil { return nil, errors.New("PrepareProposal handler not set") } @@ -479,6 +491,9 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { + span := app.tracer.StartSpan("ProcessProposal") + defer span.End() + if app.abciHandlers.ProcessProposalHandler == nil { return nil, errors.New("ProcessProposal handler not set") } @@ -576,6 +591,9 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc // height and are committed in the subsequent height, i.e. H+2. An error is // returned if vote extensions are not enabled or if extendVote fails or panics. func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (resp *abci.ResponseExtendVote, err error) { + span := app.tracer.StartSpan("ExtendVote") + defer span.End() + // Always reset state given that ExtendVote and VerifyVoteExtension can timeout // and be called again in a subsequent round. var ctx sdk.Context @@ -649,6 +667,9 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) ( // phase. The response MUST be deterministic. An error is returned if vote // extensions are not enabled or if verifyVoteExt fails or panics. func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (resp *abci.ResponseVerifyVoteExtension, err error) { + span := app.tracer.StartSpan("VerifyVoteExtension") + defer span.End() + if app.abciHandlers.VerifyVoteExtensionHandler == nil { return nil, errors.New("application VerifyVoteExtension handler not set") } @@ -717,6 +738,9 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r // only used to handle early cancellation, for anything related to state app.stateManager.GetState(execModeFinalize).Context() // must be used. func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { + span := app.tracer.StartSpan("internalFinalizeBlock") + defer span.End() + var events []abci.Event if err := app.checkHalt(req.Height, req.Time); err != nil { @@ -782,14 +806,14 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request WithHeaderHash(req.Hash)) } - preblockEvents, err := app.preBlock(req) + preblockEvents, err := app.preBlock(span, req) if err != nil { return nil, err } events = append(events, preblockEvents...) - beginBlock, err := app.beginBlock(req) + beginBlock, err := app.beginBlock(span, req) if err != nil { return nil, err } @@ -846,7 +870,7 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request WithBlockGasUsed(blockGasUsed). WithBlockGasWanted(blockGasWanted), ) - endBlock, err := app.endBlock(finalizeState.Context()) + endBlock, err := app.endBlock(span, finalizeState.Context()) if err != nil { return nil, err } @@ -874,6 +898,7 @@ func (app *BaseApp) executeTxsWithExecutor(ctx context.Context, ms storetypes.Mu if app.txRunner == nil { app.txRunner = txnrunner.NewDefaultRunner( app.txDecoder, + app.tracer, ) } @@ -891,6 +916,9 @@ func (app *BaseApp) executeTxsWithExecutor(ctx context.Context, ms storetypes.Mu // extensions into the proposal, which should not themselves be executed in cases // where they adhere to the sdk.Tx interface. func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.ResponseFinalizeBlock, err error) { + span := app.tracer.StartSpan("FinalizeBlock") + defer span.End() + defer func() { if res == nil { return @@ -958,6 +986,9 @@ func (app *BaseApp) checkHalt(height int64, time time.Time) error { // against that height and gracefully halt if it matches the latest committed // height. func (app *BaseApp) Commit() (*abci.ResponseCommit, error) { + span := app.tracer.StartSpan("Commit") + defer span.End() + finalizeState := app.stateManager.GetState(execModeFinalize) header := finalizeState.Context().BlockHeader() retainHeight := app.GetBlockRetentionHeight(header.Height) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 31fd20b1f7e7..14b42770af07 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -18,12 +18,13 @@ import ( protov2 "google.golang.org/protobuf/proto" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/log" "cosmossdk.io/store" storemetrics "cosmossdk.io/store/metrics" "cosmossdk.io/store/snapshots" storetypes "cosmossdk.io/store/types" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/baseapp/config" "github.com/cosmos/cosmos-sdk/baseapp/oe" "github.com/cosmos/cosmos-sdk/baseapp/state" @@ -64,6 +65,7 @@ type BaseApp struct { // initialized on creation mu sync.Mutex // mu protects the fields below. logger log.Logger + tracer log.Tracer name string // application name from abci.BlockInfo db dbm.DB // common DB backend cms storetypes.CommitMultiStore // Main (uncached) state @@ -186,6 +188,13 @@ func NewBaseApp( gasConfig: config.GasConfig{QueryGasLimit: math.MaxUint64}, } + // initialize tracer + tracer, ok := app.logger.(log.Tracer) + if !ok { + tracer = log.NewNopTracer() + } + app.tracer = tracer + for _, option := range options { option(app) } @@ -651,7 +660,10 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context return ctx.WithMultiStore(msCache), msCache } -func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) { +func (app *BaseApp) preBlock(tracer log.Tracer, req *abci.RequestFinalizeBlock) ([]abci.Event, error) { + span := tracer.StartSpan("preBlock") + defer span.End() + var events []abci.Event if app.abciHandlers.PreBlocker != nil { finalizeState := app.stateManager.GetState(execModeFinalize) @@ -674,7 +686,7 @@ func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, erro return events, nil } -func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) { +func (app *BaseApp) beginBlock(tracer log.Tracer, _ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) { var ( resp sdk.BeginBlock err error @@ -700,7 +712,7 @@ func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, er return resp, nil } -func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txIndex int, incarnationCache map[string]any) *abci.ExecTxResult { +func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txIndex int, incarnationCache map[string]any, tracer log.Tracer) *abci.ExecTxResult { gInfo := sdk.GasInfo{} resultStr := "successful" @@ -713,7 +725,7 @@ func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txI telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") }() - gInfo, result, anteEvents, err := app.RunTx(execModeFinalize, tx, nil, txIndex, txMultiStore, incarnationCache) + gInfo, result, anteEvents, err := app.RunTx(execModeFinalize, tx, nil, txIndex, txMultiStore, incarnationCache, tracer) if err != nil { resultStr = "failed" resp = sdkerrors.ResponseExecTxResultWithEvents( @@ -739,7 +751,10 @@ func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txI // endBlock is an application-defined function that is called after transactions // have been processed in FinalizeBlock. -func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { +func (app *BaseApp) endBlock(tracer log.Tracer, _ context.Context) (sdk.EndBlock, error) { + span := tracer.StartSpan("endBlock") + defer span.End() + var endblock sdk.EndBlock if app.abciHandlers.EndBlocker != nil { @@ -772,7 +787,10 @@ func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { // and execute successfully. An error is returned otherwise. // both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice // passing the decoded tx to runTX is optional, it will be decoded if the tx is nil -func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { +func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any, tracer log.Tracer) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { + span := tracer.StartSpan("RunTx", "txBytes", txBytes, "txIndex", txIndex, "mode", mode) + defer span.End() + // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is // determined by the GasMeter. We need access to the context to get the gas // meter, so we initialize upfront. @@ -861,7 +879,9 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // performance benefits, but it'll be more difficult to get right. anteCtx, msCache = app.cacheTxContext(ctx, txBytes) anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) + anteSpan := tracer.StartSpan("anteHandler") newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate) + anteSpan.End() if !newCtx.IsZero() { // At this point, newCtx.MultiStore() is a store branch, or something else @@ -910,6 +930,7 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // in case message processing fails. At this point, the MultiStore // is a branch of a branch. runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) + runMsgCtx = runMsgCtx.WithLogger(tracer) // attach the tracer to the context as the logger // Attempt to execute all messages and only update state if all messages pass // and we're in DeliverTx. Note, runMsgs will never return a reference to a @@ -968,6 +989,14 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // Handler does not exist for a given message route. Otherwise, a reference to a // Result is returned. The caller must not commit state if an error is returned. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode sdk.ExecMode) (*sdk.Result, error) { + // extract the tracer from the context + tracer, ok := ctx.Logger().(log.Tracer) + if !ok { + tracer = log.NewNopTracer() + } + span := tracer.StartSpan("runMsgs") + defer span.End() + events := sdk.EmptyEvents() var msgResponses []*codectypes.Any @@ -984,11 +1013,13 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) } + msgSpan := span.StartSpan("msgHandler", "msgType", sdk.MsgTypeURL(msg), "msgIndex", i) // ADR 031 request type routing msgResult, err := handler(ctx, msg) if err != nil { return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i) } + msgSpan.End() // create message events msgEvents, err := createEvents(app.cdc, msgResult.GetEvents(), msg, msgsV2[i]) @@ -1077,7 +1108,7 @@ func (app *BaseApp) PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) { return nil, err } - _, _, _, err = app.RunTx(execModePrepareProposal, bz, tx, -1, nil, nil) + _, _, _, err = app.RunTx(execModePrepareProposal, bz, tx, -1, nil, nil, app.tracer) if err != nil { return nil, err } @@ -1096,7 +1127,7 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) { return nil, err } - _, _, _, err = app.RunTx(execModeProcessProposal, txBz, tx, -1, nil, nil) + _, _, _, err = app.RunTx(execModeProcessProposal, txBz, tx, -1, nil, nil, app.tracer) if err != nil { return nil, err } diff --git a/baseapp/genesis.go b/baseapp/genesis.go index 547a1322d8a7..cbc1adc5d709 100644 --- a/baseapp/genesis.go +++ b/baseapp/genesis.go @@ -13,7 +13,7 @@ var _ genesis.TxHandler = (*BaseApp)(nil) // ExecuteGenesisTx implements genesis.GenesisState from // cosmossdk.io/core/genesis to set initial state in genesis func (ba *BaseApp) ExecuteGenesisTx(tx []byte) error { - res := ba.deliverTx(tx, nil, -1, nil) + res := ba.deliverTx(tx, nil, -1, nil, ba.tracer) if res.Code != types.CodeTypeOK { return errors.New(res.Log) diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index 6144552490b2..560f96e61834 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -5,6 +5,7 @@ import ( errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -19,13 +20,13 @@ func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, * return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeCheck, bz, tx, -1, nil, nil) + gasInfo, result, _, err := app.RunTx(execModeCheck, bz, tx, -1, nil, nil, log.NewNopTracer()) return gasInfo, result, err } // Simulate executes a tx in simulate mode to get result and gas info. func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { - gasInfo, result, _, err := app.RunTx(execModeSimulate, txBytes, nil, -1, nil, nil) + gasInfo, result, _, err := app.RunTx(execModeSimulate, txBytes, nil, -1, nil, nil, log.NewNopTracer()) return gasInfo, result, err } @@ -36,7 +37,7 @@ func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer()) return gasInfo, result, err } @@ -47,7 +48,7 @@ func (app *BaseApp) SimTxFinalizeBlock(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk. return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer()) return gasInfo, result, err } diff --git a/baseapp/txnrunner/default.go b/baseapp/txnrunner/default.go index d92f8099d4dd..01b9921fc512 100644 --- a/baseapp/txnrunner/default.go +++ b/baseapp/txnrunner/default.go @@ -7,21 +7,24 @@ import ( storetypes "cosmossdk.io/store/types" + "cosmossdk.io/log" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) var _ sdk.TxRunner = DefaultRunner{} -func NewDefaultRunner(txDecoder sdk.TxDecoder) *DefaultRunner { +func NewDefaultRunner(txDecoder sdk.TxDecoder, tracer log.Tracer) *DefaultRunner { return &DefaultRunner{ txDecoder: txDecoder, + tracer: tracer, } } // DefaultRunner is the default TxnRunner implementation which executes the transactions in a block sequentially. type DefaultRunner struct { txDecoder sdk.TxDecoder + tracer log.Tracer } func (d DefaultRunner) Run(ctx context.Context, _ storetypes.MultiStore, txs [][]byte, deliverTx sdk.DeliverTxFunc) ([]*abci.ExecTxResult, error) { @@ -31,7 +34,7 @@ func (d DefaultRunner) Run(ctx context.Context, _ storetypes.MultiStore, txs [][ var response *abci.ExecTxResult if _, err := d.txDecoder(rawTx); err == nil { - response = deliverTx(rawTx, nil, i, nil) + response = deliverTx(rawTx, nil, i, nil, d.tracer) } else { // In the case where a transaction included in a block proposal is malformed, // we still want to return a default response to comet. This is because comet diff --git a/blockstm/executor.go b/blockstm/executor.go index 4014c4ead966..8ee790c486b9 100644 --- a/blockstm/executor.go +++ b/blockstm/executor.go @@ -3,6 +3,8 @@ package blockstm import ( "context" "fmt" + + "cosmossdk.io/log" ) // Executor fields are not mutated during execution. @@ -84,6 +86,6 @@ func (e *Executor) NeedsReexecution(version TxnVersion) (TxnVersion, TaskKind) { func (e *Executor) execute(txn TxnIndex) *MultiMVMemoryView { view := e.mvMemory.View(txn) - e.txExecutor(txn, view) + e.txExecutor(txn, view, log.TracerFromContext(e.ctx)) return view } diff --git a/blockstm/txnrunner.go b/blockstm/txnrunner.go index 9e50c60522ea..eb3461e93a8a 100644 --- a/blockstm/txnrunner.go +++ b/blockstm/txnrunner.go @@ -10,6 +10,7 @@ import ( "cosmossdk.io/collections" storetypes "cosmossdk.io/store/types" + "cosmossdk.io/log" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -78,7 +79,7 @@ func (e STMRunner) Run(ctx context.Context, ms storetypes.MultiStore, txs [][]by stmMultiStoreWrapper{ms}, e.workers, estimates, - func(txn TxnIndex, ms MultiStore) { + func(txn TxnIndex, ms MultiStore, tracer log.Tracer) { var cache map[string]any // only one of the concurrent incarnations gets the cache if there are any, otherwise execute without @@ -92,7 +93,7 @@ func (e STMRunner) Run(ctx context.Context, ms storetypes.MultiStore, txs [][]by if memTxs != nil { memTx = memTxs[txn] } - results[txn] = deliverTx(memTx, msWrapper{ms}, int(txn), cache) + results[txn] = deliverTx(memTx, msWrapper{ms}, int(txn), cache, tracer) if v != nil { incarnationCache[txn].Store(v) diff --git a/blockstm/types.go b/blockstm/types.go index 873647b2f077..fa533dfdfefc 100644 --- a/blockstm/types.go +++ b/blockstm/types.go @@ -2,6 +2,8 @@ package blockstm import ( storetypes "cosmossdk.io/store/types" + + "cosmossdk.io/log" ) const ( @@ -59,7 +61,7 @@ type ReadSet struct { type MultiReadSet = map[int]*ReadSet // TxExecutor executes transactions on top of a multi-version memory view. -type TxExecutor func(TxnIndex, MultiStore) +type TxExecutor func(TxnIndex, MultiStore, log.Tracer) type MultiStore interface { GetStore(storetypes.StoreKey) storetypes.Store diff --git a/client/v2/go.mod b/client/v2/go.mod index eb6f5f9b2d1d..6c630e66cc06 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -153,7 +153,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.21.0 // indirect + golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.0 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index d153aa4782de..2a11b57d4188 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -749,8 +749,8 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= -golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/go.mod b/go.mod index b1ea3cd743be..8354446245f7 100644 --- a/go.mod +++ b/go.mod @@ -217,7 +217,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.21.0 // indirect + golang.org/x/arch v0.22.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect @@ -236,6 +236,8 @@ require ( // Here are the short-lived replace from the Cosmos SDK // Replace here are pending PRs, or version to be tagged +replace cosmossdk.io/log => ./log + // Below are the long-lived replace of the Cosmos SDK replace ( // use cosmos fork of keyring diff --git a/go.sum b/go.sum index 913cb7b9274d..2ea9388c678b 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,6 @@ cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= -cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= -cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= @@ -876,8 +874,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= -golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/simapp/go.mod b/simapp/go.mod index 0040c7a5ad21..ec751f171381 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -210,7 +210,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.21.0 // indirect + golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.0 // indirect @@ -233,6 +233,10 @@ require ( sigs.k8s.io/yaml v1.6.0 // indirect ) +replace ( + cosmossdk.io/log => ../log +) + // Below are the long-lived replace of the SimApp replace ( // use cosmos fork of keyring diff --git a/simapp/go.sum b/simapp/go.sum index 84ce76cb5ec8..5d1407fd10c2 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -884,8 +884,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= -golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/systemtests/go.mod b/systemtests/go.mod index befabb854c70..f8ed7b71fed4 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -152,7 +152,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.21.0 // indirect + golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.0 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index ff93cb36b90a..4cfa4ee4d84e 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -758,8 +758,8 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= -golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/tests/go.mod b/tests/go.mod index 6837685e3fcd..8c13fa6ff2f4 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -210,7 +210,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.21.0 // indirect + golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index 3623a6f9fc87..416fb05e6ede 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -883,8 +883,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= -golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index fb79d5e7eacb..d91bf94d9312 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -158,7 +158,7 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.21.0 // indirect + golang.org/x/arch v0.22.0 // indirect golang.org/x/crypto v0.43.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.0 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index 23efa26c0731..90428e129f04 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -756,8 +756,8 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= -golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/types/abci.go b/types/abci.go index 06ca95d7b620..d80439ed8ac0 100644 --- a/types/abci.go +++ b/types/abci.go @@ -6,6 +6,8 @@ import ( abci "github.com/cometbft/cometbft/abci/types" storetypes "cosmossdk.io/store/types" + + "cosmossdk.io/log" ) // ABCIHandlers aggregates all ABCI handlers needed for an application. @@ -99,7 +101,7 @@ func (r ResponsePreBlock) IsConsensusParamsChanged() bool { type RunTx = func(txBytes []byte, tx Tx) (gInfo GasInfo, result *Result, anteEvents []abci.Event, err error) // DeliverTxFunc is the function called for each transaction in order to produce a single ExecTxResult -type DeliverTxFunc func(tx []byte, ms storetypes.MultiStore, txIndex int, incarnationCache map[string]any) *abci.ExecTxResult +type DeliverTxFunc func(tx []byte, ms storetypes.MultiStore, txIndex int, incarnationCache map[string]any, tracer log.Tracer) *abci.ExecTxResult // TxRunner defines an interface for types which can be used to execute the DeliverTxFunc. // It should return an array of *abci.ExecTxResult corresponding to the result of executing each transaction From d5f5ea4a8bfc86f01333337b1f5c5f52091263e7 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 29 Oct 2025 22:37:47 -0400 Subject: [PATCH 38/87] latest WIP --- simapp/sim_test.go | 2 +- telemetry/file_sink.go | 157 +++++++++++++++++++++++ telemetry/file_sink_test.go | 177 ++++++++++++++++++++++++++ telemetry/metrics.go | 185 ++++++++++++++++++++++++---- telemetry/metrics_span.go | 135 ++++++++++++++++++++ telemetry/{otel.go => otel_span.go} | 0 6 files changed, 628 insertions(+), 28 deletions(-) create mode 100644 telemetry/file_sink.go create mode 100644 telemetry/file_sink_test.go create mode 100644 telemetry/metrics_span.go rename telemetry/{otel.go => otel_span.go} (100%) diff --git a/simapp/sim_test.go b/simapp/sim_test.go index c76a12735d23..55ead7b3ce7f 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -157,7 +157,7 @@ func IsEmptyValidatorSetErr(err error) bool { } func TestAppStateDeterminism(t *testing.T) { - const numTimesToRunPerSeed = 3 + const numTimesToRunPerSeed = 1 var seeds []int64 if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue { // We will be overriding the random seed and just run a single simulation on the provided seed value diff --git a/telemetry/file_sink.go b/telemetry/file_sink.go new file mode 100644 index 000000000000..4c653558fd6d --- /dev/null +++ b/telemetry/file_sink.go @@ -0,0 +1,157 @@ +package telemetry + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "sync" + "time" + + "github.com/hashicorp/go-metrics" +) + +// FileSink writes metrics to a file as JSON lines (JSONL format). +// Each metric emission creates a single JSON line with timestamp, type, key, value, and labels. +// +// This sink is particularly useful for: +// - Test environments where metrics need to be inspected after execution +// - CI/CD pipelines where metrics should be logged for analysis +// - Debugging and local development +// +// The sink is thread-safe and buffers writes for performance. +// Call Close() to flush buffered data and close the underlying writer. +type FileSink struct { + writer *bufio.Writer + closer io.Closer + mu sync.Mutex + closed bool +} + +// metricLine represents a single metric emission in JSON format. +type metricLine struct { + Timestamp time.Time `json:"timestamp"` + Type string `json:"type"` + Key []string `json:"key"` + Value float32 `json:"value"` + Labels []metrics.Label `json:"labels,omitempty"` +} + +// NewFileSink creates a new FileSink that writes to the given io.WriteCloser. +// The sink buffers writes for performance. Call Close() to flush and close. +func NewFileSink(w io.WriteCloser) *FileSink { + return &FileSink{ + writer: bufio.NewWriter(w), + closer: w, + closed: false, + } +} + +// SetGauge implements metrics.MetricSink. +func (f *FileSink) SetGauge(key []string, val float32) { + f.SetGaugeWithLabels(key, val, nil) +} + +// SetGaugeWithLabels implements metrics.MetricSink. +func (f *FileSink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label) { + f.writeLine(metricLine{ + Timestamp: time.Now().UTC(), + Type: "gauge", + Key: key, + Value: val, + Labels: labels, + }) +} + +// EmitKey implements metrics.MetricSink. +func (f *FileSink) EmitKey(key []string, val float32) { + f.writeLine(metricLine{ + Timestamp: time.Now().UTC(), + Type: "kv", + Key: key, + Value: val, + }) +} + +// IncrCounter implements metrics.MetricSink. +func (f *FileSink) IncrCounter(key []string, val float32) { + f.IncrCounterWithLabels(key, val, nil) +} + +// IncrCounterWithLabels implements metrics.MetricSink. +func (f *FileSink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label) { + f.writeLine(metricLine{ + Timestamp: time.Now().UTC(), + Type: "counter", + Key: key, + Value: val, + Labels: labels, + }) +} + +// AddSample implements metrics.MetricSink. +func (f *FileSink) AddSample(key []string, val float32) { + f.AddSampleWithLabels(key, val, nil) +} + +// AddSampleWithLabels implements metrics.MetricSink. +func (f *FileSink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label) { + f.writeLine(metricLine{ + Timestamp: time.Now().UTC(), + Type: "sample", + Key: key, + Value: val, + Labels: labels, + }) +} + +// writeLine writes a metric line to the file as JSON. +func (f *FileSink) writeLine(line metricLine) { + f.mu.Lock() + defer f.mu.Unlock() + + if f.closed { + return + } + + data, err := json.Marshal(line) + if err != nil { + // If JSON marshaling fails, write error to stderr but don't crash + fmt.Fprintf(io.Discard, "failed to marshal metric: %v\n", err) + return + } + + // Write JSON line with newline + if _, err := f.writer.Write(data); err != nil { + return + } + if err := f.writer.WriteByte('\n'); err != nil { + return + } +} + +// Close flushes any buffered data and closes the underlying writer. +// It is safe to call Close multiple times. +func (f *FileSink) Close() error { + f.mu.Lock() + defer f.mu.Unlock() + + if f.closed { + return nil + } + + f.closed = true + + // Flush buffered data + if err := f.writer.Flush(); err != nil { + f.closer.Close() // Try to close anyway + return fmt.Errorf("failed to flush metrics file: %w", err) + } + + // Close the underlying file + if err := f.closer.Close(); err != nil { + return fmt.Errorf("failed to close metrics file: %w", err) + } + + return nil +} diff --git a/telemetry/file_sink_test.go b/telemetry/file_sink_test.go new file mode 100644 index 000000000000..431bdfdbfd48 --- /dev/null +++ b/telemetry/file_sink_test.go @@ -0,0 +1,177 @@ +package telemetry + +import ( + "bufio" + "encoding/json" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/hashicorp/go-metrics" + "github.com/stretchr/testify/require" +) + +func TestFileSink_BasicOperations(t *testing.T) { + tmpfile := filepath.Join(t.TempDir(), "metrics.jsonl") + file, err := os.Create(tmpfile) + require.NoError(t, err) + + sink := NewFileSink(file) + + // Emit various metric types + sink.IncrCounter([]string{"test", "counter"}, 1.5) + sink.SetGauge([]string{"test", "gauge"}, 42.0) + sink.AddSample([]string{"test", "sample"}, 100.0) + sink.EmitKey([]string{"test", "kv"}, 3.14) + + // Emit metrics with labels + labels := []metrics.Label{ + {Name: "module", Value: "bank"}, + {Name: "operation", Value: "send"}, + } + sink.IncrCounterWithLabels([]string{"test", "counter_labeled"}, 2.0, labels) + sink.SetGaugeWithLabels([]string{"test", "gauge_labeled"}, 99.0, labels) + sink.AddSampleWithLabels([]string{"test", "sample_labeled"}, 50.0, labels) + + // Close to flush + require.NoError(t, sink.Close()) + + // Read and verify file contents + data, err := os.ReadFile(tmpfile) + require.NoError(t, err) + + file2, err := os.Open(tmpfile) + require.NoError(t, err) + defer file2.Close() + + scanner := bufio.NewScanner(file2) + lineCount := 0 + for scanner.Scan() { + lineCount++ + var metric metricLine + err := json.Unmarshal(scanner.Bytes(), &metric) + require.NoError(t, err, "line %d should be valid JSON", lineCount) + require.NotZero(t, metric.Timestamp, "line %d should have timestamp", lineCount) + require.NotEmpty(t, metric.Type, "line %d should have type", lineCount) + require.NotEmpty(t, metric.Key, "line %d should have key", lineCount) + } + require.NoError(t, scanner.Err()) + require.Equal(t, 7, lineCount, "should have 7 metrics") + + // Verify specific metric formats + require.Contains(t, string(data), `"type":"counter"`) + require.Contains(t, string(data), `"type":"gauge"`) + require.Contains(t, string(data), `"type":"sample"`) + require.Contains(t, string(data), `"type":"kv"`) + require.Contains(t, string(data), `"key":["test","counter"]`) + require.Contains(t, string(data), `"value":1.5`) + require.Contains(t, string(data), `"labels":[{"Name":"module","Value":"bank"}`) +} + +func TestFileSink_ConcurrentWrites(t *testing.T) { + tmpfile := filepath.Join(t.TempDir(), "metrics_concurrent.jsonl") + file, err := os.Create(tmpfile) + require.NoError(t, err) + + sink := NewFileSink(file) + + // Spawn multiple goroutines writing metrics concurrently + const numGoroutines = 10 + const metricsPerGoroutine = 100 + + var wg sync.WaitGroup + wg.Add(numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func(id int) { + defer wg.Done() + for j := 0; j < metricsPerGoroutine; j++ { + sink.IncrCounter([]string{"concurrent", "test"}, float32(id)) + } + }(i) + } + + wg.Wait() + require.NoError(t, sink.Close()) + + // Count lines in file + file, err = os.Open(tmpfile) + require.NoError(t, err) + defer file.Close() + + scanner := bufio.NewScanner(file) + lineCount := 0 + for scanner.Scan() { + lineCount++ + } + require.NoError(t, scanner.Err()) + require.Equal(t, numGoroutines*metricsPerGoroutine, lineCount, "all metrics should be written") +} + +func TestFileSink_CloseIdempotent(t *testing.T) { + tmpfile := filepath.Join(t.TempDir(), "metrics_close.jsonl") + file, err := os.Create(tmpfile) + require.NoError(t, err) + + sink := NewFileSink(file) + sink.IncrCounter([]string{"test"}, 1.0) + + // Close multiple times should not error + require.NoError(t, sink.Close()) + require.NoError(t, sink.Close()) + require.NoError(t, sink.Close()) +} + +func TestFileSink_WritesAfterClose(t *testing.T) { + tmpfile := filepath.Join(t.TempDir(), "metrics_after_close.jsonl") + file, err := os.Create(tmpfile) + require.NoError(t, err) + + sink := NewFileSink(file) + sink.IncrCounter([]string{"before"}, 1.0) + require.NoError(t, sink.Close()) + + // Writes after close should be silently ignored (no panic) + sink.IncrCounter([]string{"after"}, 1.0) + + // File should only contain one metric + data, err := os.ReadFile(tmpfile) + require.NoError(t, err) + + scanner := bufio.NewScanner(bufio.NewReader(os.Open(tmpfile))) + lineCount := 0 + for scanner.Scan() { + lineCount++ + } + require.Equal(t, 1, lineCount, "only metric before close should be written") + require.Contains(t, string(data), `"key":["before"]`) + require.NotContains(t, string(data), `"key":["after"]`) +} + +func TestFileSink_JSONFormat(t *testing.T) { + tmpfile := filepath.Join(t.TempDir(), "metrics_json.jsonl") + file, err := os.Create(tmpfile) + require.NoError(t, err) + + sink := NewFileSink(file) + labels := []metrics.Label{{Name: "env", Value: "test"}} + sink.IncrCounterWithLabels([]string{"api", "requests"}, 5.0, labels) + require.NoError(t, sink.Close()) + + // Parse JSON and verify structure + data, err := os.ReadFile(tmpfile) + require.NoError(t, err) + + var metric metricLine + err = json.Unmarshal(data[:len(data)-1], &metric) // Remove trailing newline + require.NoError(t, err) + + require.Equal(t, "counter", metric.Type) + require.Equal(t, []string{"api", "requests"}, metric.Key) + require.Equal(t, float32(5.0), metric.Value) + require.Len(t, metric.Labels, 1) + require.Equal(t, "env", metric.Labels[0].Name) + require.Equal(t, "test", metric.Labels[0].Value) + require.NotZero(t, metric.Timestamp) +} diff --git a/telemetry/metrics.go b/telemetry/metrics.go index 67ace50c53ec..cae3dd2d3701 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -1,3 +1,41 @@ +// Package telemetry provides observability through metrics and distributed tracing. +// +// # Metrics Collection +// +// Metrics collection uses hashicorp/go-metrics with support for multiple sink backends: +// - mem: In-memory aggregation with SIGUSR1 signal dumping to stderr +// - prometheus: Prometheus registry for pull-based scraping via /metrics endpoint +// - statsd: Push-based metrics to StatsD daemon +// - dogstatsd: Push-based metrics to Datadog StatsD daemon with tagging +// - file: Write metrics to a file as JSON lines (useful for tests and debugging) +// +// Multiple sinks can be active simultaneously via FanoutSink (e.g., both in-memory and Prometheus). +// +// # Distributed Tracing +// +// Tracing support is provided via OtelSpan, which wraps OpenTelemetry for hierarchical span tracking. +// See otel.go for the log.Tracer implementation. +// +// # Usage +// +// Initialize metrics at application startup: +// +// m, err := telemetry.New(telemetry.Config{ +// Enabled: true, +// ServiceName: "cosmos-app", +// PrometheusRetentionTime: 60, +// GlobalLabels: [][]string{{"chain_id", "cosmoshub-1"}}, +// }) +// if err != nil { +// log.Fatal(err) +// } +// defer m.Close() +// +// Emit metrics from anywhere in the application: +// +// telemetry.IncrCounter(1, "tx", "processed") +// telemetry.SetGauge(1024, "mempool", "size") +// defer telemetry.MeasureSince(telemetry.Now(), "block", "execution") package telemetry import ( @@ -5,7 +43,9 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" + "os" "time" "github.com/hashicorp/go-metrics" @@ -43,6 +83,7 @@ const ( MetricSinkInMem = "mem" MetricSinkStatsd = "statsd" MetricSinkDogsStatsd = "dogstatsd" + MetricSinkFile = "file" ) // DisplayableSink is an interface that defines a method for displaying metrics. @@ -52,63 +93,121 @@ type DisplayableSink interface { // Config defines the configuration options for application telemetry. type Config struct { - // Prefixed with keys to separate services + // ServiceName is the identifier for this service, used as a prefix for all metric keys. + // Example: "cosmos-app" → metrics like "cosmos-app.tx.count" ServiceName string `mapstructure:"service-name"` - // Enabled enables the application telemetry functionality. When enabled, - // an in-memory sink is also enabled by default. Operators may also enabled - // other sinks such as Prometheus. + // Enabled controls whether telemetry is active. When false, all telemetry operations + // become no-ops with zero overhead. When true, metrics collection is activated. Enabled bool `mapstructure:"enabled"` - // Enable prefixing gauge values with hostname + // EnableHostname prefixes gauge values with the hostname. + // Useful in multi-node deployments to identify which node emitted a metric. EnableHostname bool `mapstructure:"enable-hostname"` - // Enable adding hostname to labels + // EnableHostnameLabel adds a "hostname" label to all metrics. + // Alternative to EnableHostname that works better with label-based systems like Prometheus. EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` - // Enable adding service to labels + // EnableServiceLabel adds a "service" label with the ServiceName to all metrics. + // Useful when aggregating metrics from multiple services in one monitoring system. EnableServiceLabel bool `mapstructure:"enable-service-label"` // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. - // It defines the retention duration in seconds. + // Defines how long (in seconds) metrics are retained in memory for scraping. + // The Prometheus sink is added to a FanoutSink alongside the primary sink. + // Recommended value: 60 seconds or more. PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` - // GlobalLabels defines a global set of name/value label tuples applied to all - // metrics emitted using the wrapper functions defined in telemetry package. + // GlobalLabels defines a set of key-value label pairs applied to ALL metrics. + // These labels are automatically attached to every metric emission. + // Useful for static identifiers like chain ID, environment, region, etc. // - // Example: - // [["chain_id", "cosmoshub-1"]] + // Example: [][]string{{"chain_id", "cosmoshub-1"}, {"env", "production"}} + // + // Note: The outer array contains label pairs, each inner array has exactly 2 elements [key, value]. GlobalLabels [][]string `mapstructure:"global-labels"` - // MetricsSink defines the type of metrics backend to use. + // MetricsSink defines the metrics backend type. Supported values: + // - "mem" (default): In-memory sink with SIGUSR1 dump-to-stderr capability + // - "prometheus": Prometheus exposition format (use with PrometheusRetentionTime) + // - "statsd": StatsD protocol (push-based, requires StatsdAddr) + // - "dogstatsd": Datadog-enhanced StatsD with tags (requires StatsdAddr, DatadogHostname) + // - "file": JSON lines written to a file (requires MetricsFile) + // + // Multiple sinks can be active via FanoutSink (e.g., mem + prometheus). MetricsSink string `mapstructure:"metrics-sink" default:"mem"` - // StatsdAddr defines the address of a statsd server to send metrics to. - // Only utilized if MetricsSink is set to "statsd" or "dogstatsd". + // StatsdAddr is the address of the StatsD or DogStatsD server (host:port). + // Only used when MetricsSink is "statsd" or "dogstatsd". + // Example: "localhost:8125" StatsdAddr string `mapstructure:"statsd-addr"` - // DatadogHostname defines the hostname to use when emitting metrics to - // Datadog. Only utilized if MetricsSink is set to "dogstatsd". + // DatadogHostname is the hostname to report when using DogStatsD. + // Only used when MetricsSink is "dogstatsd". + // If empty, the system hostname is used. DatadogHostname string `mapstructure:"datadog-hostname"` + + // MetricsFile is the file path to write metrics to in JSONL format. + // Only used when MetricsSink is "file". + // Each metric emission creates a JSON line: {"timestamp":"...","type":"counter","key":[...],"value":1.0} + // Example: "/tmp/metrics.jsonl" or "./metrics.jsonl" + MetricsFile string `mapstructure:"metrics-file"` + + TraceSink string `mapstructure:"trace-sink"` } -// Metrics defines a wrapper around application telemetry functionality. It allows -// metrics to be gathered at any point in time. When creating a Metrics object, -// internally, a global metrics is registered with a set of sinks as configured -// by the operator. In addition to the sinks, when a process gets a SIGUSR1, a -// dump of formatted recent metrics will be sent to STDERR. +// Metrics provides access to the application's metrics collection system. +// It wraps the go-metrics global registry and configured sinks. +// +// When using the in-memory sink, sending SIGUSR1 to the process (kill -USR1 ) +// will dump current metrics to stderr for debugging. +// +// The Metrics object maintains references to configured sinks and provides +// a Gather() method for pull-based metric retrieval (useful for testing and monitoring). +// +// When using the file sink, call Close() to flush buffered data and close the file. +// +// Note: go-metrics uses a singleton global registry. Only one Metrics instance +// should be created per process. type Metrics struct { sink metrics.MetricSink prometheusEnabled bool + closer io.Closer // non-nil when using file sink } -// GatherResponse is the response type of registered metrics +// GatherResponse contains collected metrics in the requested format. +// The Metrics field holds the serialized metric data, and ContentType +// indicates how it's encoded ("application/json" or prometheus text format). type GatherResponse struct { Metrics []byte ContentType string } -// New creates a new instance of Metrics +// New creates and initializes the metrics system with the given configuration. +// +// Returns nil if telemetry is disabled (cfg.Enabled == false), which allows +// callers to safely ignore the Metrics object. +// +// The function: +// - Initializes the go-metrics global registry +// - Configures the specified sink(s) (mem, prometheus, statsd, dogstatsd, file) +// - Sets up global labels to be applied to all metrics +// - Enables SIGUSR1 signal handling for in-memory sink dumps +// - Creates a FanoutSink if multiple sinks are needed (e.g., mem + prometheus) +// +// Example: +// +// m, err := telemetry.New(telemetry.Config{ +// Enabled: true, +// ServiceName: "cosmos-app", +// MetricsSink: telemetry.MetricSinkInMem, +// PrometheusRetentionTime: 60, +// }) +// if err != nil { +// return err +// } +// defer m.Close() func New(cfg Config) (_ *Metrics, rerr error) { globalTelemetryEnabled = cfg.Enabled if !cfg.Enabled { @@ -128,14 +227,26 @@ func New(cfg Config) (_ *Metrics, rerr error) { metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel var ( - sink metrics.MetricSink - err error + sink metrics.MetricSink + closer io.Closer + err error ) switch cfg.MetricsSink { case MetricSinkStatsd: sink, err = metrics.NewStatsdSink(cfg.StatsdAddr) case MetricSinkDogsStatsd: sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname) + case MetricSinkFile: + if cfg.MetricsFile == "" { + return nil, errors.New("metrics-file must be set when metrics-sink is 'file'") + } + file, err := os.OpenFile(cfg.MetricsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return nil, fmt.Errorf("failed to open metrics file: %w", err) + } + fileSink := NewFileSink(file) + sink = fileSink + closer = fileSink default: memSink := metrics.NewInmemSink(10*time.Second, time.Minute) sink = memSink @@ -151,7 +262,7 @@ func New(cfg Config) (_ *Metrics, rerr error) { return nil, err } - m := &Metrics{sink: sink} + m := &Metrics{sink: sink, closer: closer} fanout := metrics.FanoutSink{sink} if cfg.PrometheusRetentionTime > 0 { @@ -239,3 +350,23 @@ func (m *Metrics) gatherGeneric() (GatherResponse, error) { return GatherResponse{ContentType: "application/json", Metrics: content}, nil } + +// Close flushes any buffered data and closes resources associated with the metrics system. +// This is primarily needed when using the file sink to ensure all data is written to disk. +// It is safe to call Close() multiple times, and safe to call on a nil Metrics object. +// +// For other sink types (mem, statsd, prometheus), Close() is a no-op. +// +// Example: +// +// m, err := telemetry.New(cfg) +// if err != nil { +// return err +// } +// defer m.Close() +func (m *Metrics) Close() error { + if m == nil || m.closer == nil { + return nil + } + return m.closer.Close() +} diff --git a/telemetry/metrics_span.go b/telemetry/metrics_span.go new file mode 100644 index 000000000000..859d004bff9a --- /dev/null +++ b/telemetry/metrics_span.go @@ -0,0 +1,135 @@ +package telemetry + +import ( + "context" + "time" + + "github.com/hashicorp/go-metrics" + + "cosmossdk.io/log" +) + +// MetricsSpan is a log.Span implementation that emits timing and count metrics +// to go-metrics when the span ends. +// +// Unlike distributed tracing spans, MetricsSpan: +// - Does not support logging (Info/Warn/Error/Debug are no-ops) +// - Ignores span attributes (kvs parameters) +// - Emits aggregated metrics rather than individual trace events +// +// When End() is called, two metrics are emitted: +// - A timer metric with ".time" suffix (e.g., "query.get.time") +// - A counter metric with ".count" suffix (e.g., "query.get.count") +// +// Root path and labels are preserved across all spans created from this tracer, +// ensuring consistent metric namespacing and labeling throughout the span hierarchy. +type MetricsSpan struct { + metrics *metrics.Metrics + start time.Time + path []string + rootPath []string // Base path set at tracer creation, preserved across all spans + rootLabels []metrics.Label // Labels applied to all metrics emitted by this tracer +} + +// NewMetricsTracer creates a new MetricsSpan that acts as a root tracer. +// The rootPath defines the base metric name. If empty, metrics start from the root. +// The rootLabels are applied to all metrics emitted by this tracer and its children. +// +// Example: +// +// labels := []metrics.Label{{Name: "module", Value: "staking"}} +// tracer := NewMetricsTracer(metrics, []string{"app", "tx"}, labels) +// span := tracer.StartSpan("validate") +// defer span.End() +// // Emits: "app.tx.validate.time" and "app.tx.validate.count" with module=staking label +func NewMetricsTracer(m *metrics.Metrics, rootPath []string, rootLabels []metrics.Label) *MetricsSpan { + return &MetricsSpan{ + metrics: m, + path: rootPath, + rootPath: rootPath, + rootLabels: rootLabels, + start: time.Now(), + } +} + +// Logger methods are no-ops - MetricsSpan does not support logging. +func (m *MetricsSpan) Info(msg string, keyVals ...any) {} +func (m *MetricsSpan) Warn(msg string, keyVals ...any) {} +func (m *MetricsSpan) Error(msg string, keyVals ...any) {} +func (m *MetricsSpan) Debug(msg string, keyVals ...any) {} +func (m *MetricsSpan) With(keyVals ...any) log.Logger { return m } +func (m *MetricsSpan) Impl() any { return nil } + +// StartSpan creates a child span by appending the operation name to the current path. +// Root path and labels are preserved in the child span. +// The kvs parameters are ignored (metrics don't support dynamic attributes). +func (m *MetricsSpan) StartSpan(operation string, kvs ...any) log.Span { + path := make([]string, len(m.path)+1) + copy(path, m.path) + path[len(path)-1] = operation + return &MetricsSpan{ + metrics: m.metrics, + path: path, + rootPath: m.rootPath, + rootLabels: m.rootLabels, + start: time.Now(), + } +} + +// StartSpanContext creates a child span and returns the context unchanged. +// The span is not stored in the context. +func (m *MetricsSpan) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + return ctx, m.StartSpan(operation, kvs...) +} + +// StartRootSpan creates a new root span by combining the tracer's root path with the operation. +// Unlike StartSpan, this does not extend the current span's path - it starts fresh from the root path. +// Root labels are preserved in the new span. +// +// Example: +// +// tracer := NewMetricsTracer(metrics, []string{"app"}, nil) +// _, span := tracer.StartRootSpan(ctx, "process") +// defer span.End() +// // Emits: "app.process.time" and "app.process.count" +func (m *MetricsSpan) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + // Preserve root path and add operation to it + rootPath := append([]string{}, m.rootPath...) + rootPath = append(rootPath, operation) + + return ctx, &MetricsSpan{ + metrics: m.metrics, + path: rootPath, + rootPath: m.rootPath, + rootLabels: m.rootLabels, + start: time.Now(), + } +} + +// SetAttrs is a no-op - metrics don't support dynamic attributes. +func (m *MetricsSpan) SetAttrs(kvs ...any) {} + +// SetErr is a no-op but returns the error unchanged for convenience. +func (m *MetricsSpan) SetErr(err error, kvs ...any) error { return err } + +// End emits timing and count metrics with ".time" and ".count" suffixes. +// +// For a span with path ["query", "get"], this emits: +// - Timer: "query.get.time" with duration since start +// - Counter: "query.get.count" incremented by 1 +func (m *MetricsSpan) End() { + // Create paths with suffixes to avoid metric type conflicts + timePath := make([]string, len(m.path)+1) + copy(timePath, m.path) + timePath[len(timePath)-1] = "time" + + countPath := make([]string, len(m.path)+1) + copy(countPath, m.path) + countPath[len(countPath)-1] = "count" + + m.metrics.MeasureSince(timePath, m.start) + m.metrics.IncrCounter(countPath, 1) +} + +var _ log.Span = (*MetricsSpan)(nil) +var _ log.Tracer = (*MetricsSpan)(nil) diff --git a/telemetry/otel.go b/telemetry/otel_span.go similarity index 100% rename from telemetry/otel.go rename to telemetry/otel_span.go From 47f83e810fd19b583cbf277ee787625b215e06e3 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 12:24:42 -0400 Subject: [PATCH 39/87] add trace exporter setup --- go.mod | 21 ++- go.sum | 42 +++-- log/tracer.go | 18 ++- telemetry/config.go | 150 ++++++++++++++++++ telemetry/doc.go | 39 +++++ telemetry/file_sink_test.go | 7 +- telemetry/metrics.go | 298 ++++++++++++++++-------------------- telemetry/metrics_span.go | 99 +++++++----- telemetry/metrics_test.go | 33 +++- telemetry/otel_span.go | 46 +++++- 10 files changed, 512 insertions(+), 241 deletions(-) create mode 100644 telemetry/config.go create mode 100644 telemetry/doc.go diff --git a/go.mod b/go.mod index 8354446245f7..d696ee4f4640 100644 --- a/go.mod +++ b/go.mod @@ -57,10 +57,17 @@ require ( github.com/tendermint/go-amino v0.16.0 github.com/test-go/testify v1.1.4 github.com/tidwall/btree v1.8.1 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/mock v0.6.0 golang.org/x/crypto v0.43.0 golang.org/x/sync v0.17.0 - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 gotest.tools/v3 v3.5.2 @@ -110,6 +117,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect @@ -154,6 +162,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -208,11 +217,9 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect @@ -227,7 +234,7 @@ require ( golang.org/x/time v0.13.0 // indirect google.golang.org/api v0.247.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.17 // indirect rsc.io/qr v0.2.0 // indirect diff --git a/go.sum b/go.sum index 2ea9388c678b..693c2dcbb741 100644 --- a/go.sum +++ b/go.sum @@ -157,6 +157,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -445,6 +447,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow= @@ -835,19 +839,29 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1087,10 +1101,10 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= diff --git a/log/tracer.go b/log/tracer.go index cfb99eafdb57..00274bd2414a 100644 --- a/log/tracer.go +++ b/log/tracer.go @@ -2,13 +2,8 @@ package log import "context" -// Tracer is an interface for creating and managing spans. -// It may be backed by open telemetry or other tracing libraries, -// Spans may also be used for collecting timing metrics. -// It embeds the Logger interface. Log events may be associated with spans. -type Tracer interface { - Logger - +// TraceProvider is an interface for creating and managing tracing spans. +type TraceProvider interface { // StartSpan starts a new span with the given operation name and key-value pair attributes. // If there is a parent span, the new span will be a child of that span. // It is recommended to use a defer statement to end the span like this: @@ -34,6 +29,15 @@ type Tracer interface { StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, Span) } +// Tracer is an interface for creating and managing spans. +// It may be backed by open telemetry or other tracing libraries, +// Spans may also be used for collecting timing metrics. +// It embeds the Logger interface. Log events may be associated with spans. +type Tracer interface { + Logger + TraceProvider +} + // Span is an interface for managing spans and creating nested spans via the embedded Tracer interface. type Span interface { // Tracer is embedded to allow for the creation of nested spans. diff --git a/telemetry/config.go b/telemetry/config.go new file mode 100644 index 000000000000..e707a127251d --- /dev/null +++ b/telemetry/config.go @@ -0,0 +1,150 @@ +package telemetry + +import ( + "net/http" + + "github.com/hashicorp/go-metrics" + "github.com/prometheus/common/expfmt" +) + +// globalTelemetryEnabled is a private variable that stores the telemetry enabled state. +// It is set on initialization and does not change for the lifetime of the program. +var globalTelemetryEnabled bool + +// IsTelemetryEnabled provides controlled access to check if telemetry is enabled. +func IsTelemetryEnabled() bool { + return globalTelemetryEnabled +} + +// EnableTelemetry allows for the global telemetry enabled state to be set. +func EnableTelemetry() { + globalTelemetryEnabled = true +} + +// globalLabels defines the set of global labels that will be applied to all +// metrics emitted using the telemetry package function wrappers. +var globalLabels = []metrics.Label{} + +// Metrics supported format types. +const ( + FormatDefault = "" + FormatPrometheus = "prometheus" + FormatText = "text" + ContentTypeText = `text/plain; version=` + expfmt.TextVersion + `; charset=utf-8` + + MetricSinkInMem = "mem" + MetricSinkStatsd = "statsd" + MetricSinkDogsStatsd = "dogstatsd" + MetricSinkFile = "file" + + TraceSinkOtel = "otel" + TraceSinkNoop = "noop" +) + +// DisplayableSink is an interface that defines a method for displaying metrics. +type DisplayableSink interface { + DisplayMetrics(resp http.ResponseWriter, req *http.Request) (any, error) +} + +// Config defines the configuration options for application telemetry. +type Config struct { + // ServiceName is the identifier for this service, used as a prefix for all metric keys. + // Example: "cosmos-app" → metrics like "cosmos-app.tx.count" + ServiceName string `mapstructure:"service-name"` + + // Enabled controls whether telemetry is active. When false, all telemetry operations + // become no-ops with zero overhead. When true, metrics collection is activated. + Enabled bool `mapstructure:"enabled"` + + // EnableHostname prefixes gauge values with the hostname. + // Useful in multi-node deployments to identify which node emitted a metric. + EnableHostname bool `mapstructure:"enable-hostname"` + + // EnableHostnameLabel adds a "hostname" label to all metrics. + // Alternative to EnableHostname that works better with label-based systems like Prometheus. + EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` + + // EnableServiceLabel adds a "service" label with the ServiceName to all metrics. + // Useful when aggregating metrics from multiple services in one monitoring system. + EnableServiceLabel bool `mapstructure:"enable-service-label"` + + // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. + // Defines how long (in seconds) metrics are retained in memory for scraping. + // The Prometheus sink is added to a FanoutSink alongside the primary sink. + // Recommended value: 60 seconds or more. + PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` + + // GlobalLabels defines a set of key-value label pairs applied to ALL metrics. + // These labels are automatically attached to every metric emission. + // Useful for static identifiers like chain ID, environment, region, etc. + // + // Example: [][]string{{"chain_id", "cosmoshub-1"}, {"env", "production"}} + // + // Note: The outer array contains label pairs, each inner array has exactly 2 elements [key, value]. + GlobalLabels [][]string `mapstructure:"global-labels"` + + // MetricsSink defines the metrics backend type. Supported values: + // - "mem" (default): In-memory sink with SIGUSR1 dump-to-stderr capability + // - "prometheus": Prometheus exposition format (use with PrometheusRetentionTime) + // - "statsd": StatsD protocol (push-based, requires StatsdAddr) + // - "dogstatsd": Datadog-enhanced StatsD with tags (requires StatsdAddr, DatadogHostname) + // - "file": JSON lines written to a file (requires MetricsFile) + // + // Multiple sinks can be active via FanoutSink (e.g., mem + prometheus). + MetricsSink string `mapstructure:"metrics-sink" default:"mem"` + + // StatsdAddr is the address of the StatsD or DogStatsD server (host:port). + // Only used when MetricsSink is "statsd" or "dogstatsd". + // Example: "localhost:8125" + StatsdAddr string `mapstructure:"statsd-addr"` + + // DatadogHostname is the hostname to report when using DogStatsD. + // Only used when MetricsSink is "dogstatsd". + // If empty, the system hostname is used. + DatadogHostname string `mapstructure:"datadog-hostname"` + + // MetricsFile is the file path to write metrics to in JSONL format. + // Only used when MetricsSink is "file". + // Each metric emission creates a JSON line: {"timestamp":"...","type":"counter","key":[...],"value":1.0} + // Example: "/tmp/metrics.jsonl" or "./metrics.jsonl" + MetricsFile string `mapstructure:"metrics-file"` + + // TraceSink is the sink for trace data. Supported values: + // - "otel": OpenTelemetry trace sink + // - "metrics": all spans will be redirected to emit invocation counter and timing histogram metrics + // - "noop": No-op trace sink (default) + TraceSink string `mapstructure:"trace-sink"` + + // OtelTraceExporters is a list of OTLP exporters to use for trace data. + // This is only used when trace sink is set to "otel". + OtelTraceExporters []OtelTraceExportConfig `mapstructure:"otel-trace-exporters"` +} + +type OtelTraceExportConfig struct { + // Type is the exporter type. + // Must be one of: + // - "stdout" + // - "otlp" + // + // OTLP exporters must set the endpoint URL and can optionally set the transport protocol. + Type string `mapstructure:"type"` + + // OTLPTransport is the transport protocol to use for OTLP. + // Must be one of: + // - "http" (default) + // - "grpc" + OTLPTransport string `mapstructure:"otlp-transport"` + + // Endpoint is the OTLP exporter endpoint URL (grpc or http). + Endpoint string `mapstructure:"endpoint"` + + // Insecure disables TLS certificate verification for OTLP exporters. + Insecure bool `mapstructure:"insecure"` + + // File is the file path to write trace data to when using the "stdout" exporter. + // If it is empty, the trace data is written to stdout. + File string `mapstructure:"file"` + + // PrettyPrint enables pretty-printing of JSON output when using the "stdout" exporter. + PrettyPrint bool `mapstructure:"pretty-print"` +} diff --git a/telemetry/doc.go b/telemetry/doc.go new file mode 100644 index 000000000000..98afc159ba42 --- /dev/null +++ b/telemetry/doc.go @@ -0,0 +1,39 @@ +// Package telemetry provides observability through metrics and distributed tracing. +// +// # Metrics Collection +// +// Metrics collection uses hashicorp/go-metrics with support for multiple sink backends: +// - mem: In-memory aggregation with SIGUSR1 signal dumping to stderr +// - prometheus: Prometheus registry for pull-based scraping via /metrics endpoint +// - statsd: Push-based metrics to StatsD daemon +// - dogstatsd: Push-based metrics to Datadog StatsD daemon with tagging +// - file: Write metrics to a file as JSON lines (useful for tests and debugging) +// +// Multiple sinks can be active simultaneously via FanoutSink (e.g., both in-memory and Prometheus). +// +// # Distributed Tracing +// +// Tracing support is provided via OtelSpan, which wraps OpenTelemetry for hierarchical span tracking. +// See otel.go for the log.Tracer implementation. +// +// # Usage +// +// Initialize metrics at application startup: +// +// m, err := telemetry.New(telemetry.Config{ +// Enabled: true, +// ServiceName: "cosmos-app", +// PrometheusRetentionTime: 60, +// GlobalLabels: [][]string{{"chain_id", "cosmoshub-1"}}, +// }) +// if err != nil { +// log.Fatal(err) +// } +// defer m.Close() +// +// Emit metrics from anywhere in the application: +// +// telemetry.IncrCounter(1, "tx", "processed") +// telemetry.SetGauge(1024, "mempool", "size") +// defer telemetry.MeasureSince(telemetry.Now(), "block", "execution") +package telemetry diff --git a/telemetry/file_sink_test.go b/telemetry/file_sink_test.go index 431bdfdbfd48..4a8feceb7e37 100644 --- a/telemetry/file_sink_test.go +++ b/telemetry/file_sink_test.go @@ -139,11 +139,16 @@ func TestFileSink_WritesAfterClose(t *testing.T) { data, err := os.ReadFile(tmpfile) require.NoError(t, err) - scanner := bufio.NewScanner(bufio.NewReader(os.Open(tmpfile))) + file3, err := os.Open(tmpfile) + require.NoError(t, err) + defer file3.Close() + + scanner := bufio.NewScanner(file3) lineCount := 0 for scanner.Scan() { lineCount++ } + require.NoError(t, scanner.Err()) require.Equal(t, 1, lineCount, "only metric before close should be written") require.Contains(t, string(data), `"key":["before"]`) require.NotContains(t, string(data), `"key":["after"]`) diff --git a/telemetry/metrics.go b/telemetry/metrics.go index cae3dd2d3701..405bb3bc7a59 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -1,50 +1,11 @@ -// Package telemetry provides observability through metrics and distributed tracing. -// -// # Metrics Collection -// -// Metrics collection uses hashicorp/go-metrics with support for multiple sink backends: -// - mem: In-memory aggregation with SIGUSR1 signal dumping to stderr -// - prometheus: Prometheus registry for pull-based scraping via /metrics endpoint -// - statsd: Push-based metrics to StatsD daemon -// - dogstatsd: Push-based metrics to Datadog StatsD daemon with tagging -// - file: Write metrics to a file as JSON lines (useful for tests and debugging) -// -// Multiple sinks can be active simultaneously via FanoutSink (e.g., both in-memory and Prometheus). -// -// # Distributed Tracing -// -// Tracing support is provided via OtelSpan, which wraps OpenTelemetry for hierarchical span tracking. -// See otel.go for the log.Tracer implementation. -// -// # Usage -// -// Initialize metrics at application startup: -// -// m, err := telemetry.New(telemetry.Config{ -// Enabled: true, -// ServiceName: "cosmos-app", -// PrometheusRetentionTime: 60, -// GlobalLabels: [][]string{{"chain_id", "cosmoshub-1"}}, -// }) -// if err != nil { -// log.Fatal(err) -// } -// defer m.Close() -// -// Emit metrics from anywhere in the application: -// -// telemetry.IncrCounter(1, "tx", "processed") -// telemetry.SetGauge(1024, "mempool", "size") -// defer telemetry.MeasureSince(telemetry.Now(), "block", "execution") package telemetry import ( "bytes" + "context" "encoding/json" "errors" "fmt" - "io" - "net/http" "os" "time" @@ -53,110 +14,15 @@ import ( metricsprom "github.com/hashicorp/go-metrics/prometheus" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/expfmt" -) - -// globalTelemetryEnabled is a private variable that stores the telemetry enabled state. -// It is set on initialization and does not change for the lifetime of the program. -var globalTelemetryEnabled bool - -// IsTelemetryEnabled provides controlled access to check if telemetry is enabled. -func IsTelemetryEnabled() bool { - return globalTelemetryEnabled -} - -// EnableTelemetry allows for the global telemetry enabled state to be set. -func EnableTelemetry() { - globalTelemetryEnabled = true -} + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + otelsdktrace "go.opentelemetry.io/otel/sdk/trace" -// globalLabels defines the set of global labels that will be applied to all -// metrics emitted using the telemetry package function wrappers. -var globalLabels = []metrics.Label{} - -// Metrics supported format types. -const ( - FormatDefault = "" - FormatPrometheus = "prometheus" - FormatText = "text" - ContentTypeText = `text/plain; version=` + expfmt.TextVersion + `; charset=utf-8` - - MetricSinkInMem = "mem" - MetricSinkStatsd = "statsd" - MetricSinkDogsStatsd = "dogstatsd" - MetricSinkFile = "file" + "cosmossdk.io/log" ) -// DisplayableSink is an interface that defines a method for displaying metrics. -type DisplayableSink interface { - DisplayMetrics(resp http.ResponseWriter, req *http.Request) (any, error) -} - -// Config defines the configuration options for application telemetry. -type Config struct { - // ServiceName is the identifier for this service, used as a prefix for all metric keys. - // Example: "cosmos-app" → metrics like "cosmos-app.tx.count" - ServiceName string `mapstructure:"service-name"` - - // Enabled controls whether telemetry is active. When false, all telemetry operations - // become no-ops with zero overhead. When true, metrics collection is activated. - Enabled bool `mapstructure:"enabled"` - - // EnableHostname prefixes gauge values with the hostname. - // Useful in multi-node deployments to identify which node emitted a metric. - EnableHostname bool `mapstructure:"enable-hostname"` - - // EnableHostnameLabel adds a "hostname" label to all metrics. - // Alternative to EnableHostname that works better with label-based systems like Prometheus. - EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` - - // EnableServiceLabel adds a "service" label with the ServiceName to all metrics. - // Useful when aggregating metrics from multiple services in one monitoring system. - EnableServiceLabel bool `mapstructure:"enable-service-label"` - - // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. - // Defines how long (in seconds) metrics are retained in memory for scraping. - // The Prometheus sink is added to a FanoutSink alongside the primary sink. - // Recommended value: 60 seconds or more. - PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` - - // GlobalLabels defines a set of key-value label pairs applied to ALL metrics. - // These labels are automatically attached to every metric emission. - // Useful for static identifiers like chain ID, environment, region, etc. - // - // Example: [][]string{{"chain_id", "cosmoshub-1"}, {"env", "production"}} - // - // Note: The outer array contains label pairs, each inner array has exactly 2 elements [key, value]. - GlobalLabels [][]string `mapstructure:"global-labels"` - - // MetricsSink defines the metrics backend type. Supported values: - // - "mem" (default): In-memory sink with SIGUSR1 dump-to-stderr capability - // - "prometheus": Prometheus exposition format (use with PrometheusRetentionTime) - // - "statsd": StatsD protocol (push-based, requires StatsdAddr) - // - "dogstatsd": Datadog-enhanced StatsD with tags (requires StatsdAddr, DatadogHostname) - // - "file": JSON lines written to a file (requires MetricsFile) - // - // Multiple sinks can be active via FanoutSink (e.g., mem + prometheus). - MetricsSink string `mapstructure:"metrics-sink" default:"mem"` - - // StatsdAddr is the address of the StatsD or DogStatsD server (host:port). - // Only used when MetricsSink is "statsd" or "dogstatsd". - // Example: "localhost:8125" - StatsdAddr string `mapstructure:"statsd-addr"` - - // DatadogHostname is the hostname to report when using DogStatsD. - // Only used when MetricsSink is "dogstatsd". - // If empty, the system hostname is used. - DatadogHostname string `mapstructure:"datadog-hostname"` - - // MetricsFile is the file path to write metrics to in JSONL format. - // Only used when MetricsSink is "file". - // Each metric emission creates a JSON line: {"timestamp":"...","type":"counter","key":[...],"value":1.0} - // Example: "/tmp/metrics.jsonl" or "./metrics.jsonl" - MetricsFile string `mapstructure:"metrics-file"` - - TraceSink string `mapstructure:"trace-sink"` -} - // Metrics provides access to the application's metrics collection system. // It wraps the go-metrics global registry and configured sinks. // @@ -171,9 +37,12 @@ type Config struct { // Note: go-metrics uses a singleton global registry. Only one Metrics instance // should be created per process. type Metrics struct { - sink metrics.MetricSink - prometheusEnabled bool - closer io.Closer // non-nil when using file sink + sink metrics.MetricSink + prometheusEnabled bool + startFuncs []func(ctx context.Context) error + shutdownFuncs []func(context.Context) error + traceProvider log.TraceProvider + metricsTraceProvider *MetricsTraceProvider } // GatherResponse contains collected metrics in the requested format. @@ -226,10 +95,12 @@ func New(cfg Config) (_ *Metrics, rerr error) { metricsConf.EnableHostname = cfg.EnableHostname metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel + var startFuncs []func(context.Context) error + var shutdownFuncs []func(context.Context) error + var ( - sink metrics.MetricSink - closer io.Closer - err error + sink metrics.MetricSink + err error ) switch cfg.MetricsSink { case MetricSinkStatsd: @@ -246,7 +117,9 @@ func New(cfg Config) (_ *Metrics, rerr error) { } fileSink := NewFileSink(file) sink = fileSink - closer = fileSink + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + return fileSink.Close() + }) default: memSink := metrics.NewInmemSink(10*time.Second, time.Minute) sink = memSink @@ -258,11 +131,89 @@ func New(cfg Config) (_ *Metrics, rerr error) { }() } + metricsTraceProvider := NewMetricsTraceProvider(nil, globalLabels, metrics.Default()) + + var tracerBase log.TraceProvider + switch cfg.TraceSink { + case TraceSinkOtel: + var tracerProviderOpts []otelsdktrace.TracerProviderOption + for _, exporterOpts := range cfg.OtelTraceExporters { + switch exporterOpts.Type { + case "otlp": + endpoint := exporterOpts.Endpoint + if endpoint == "" { + return nil, fmt.Errorf("otlp endpoint must be set") + } + var client otlptrace.Client + switch exporterOpts.OTLPTransport { + case "grpc": + opts := []otlptracegrpc.Option{otlptracegrpc.WithEndpoint(endpoint)} + if exporterOpts.Insecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + client = otlptracegrpc.NewClient(opts...) + default: + opts := []otlptracehttp.Option{otlptracehttp.WithEndpoint(endpoint)} + if exporterOpts.Insecure { + opts = append(opts, otlptracehttp.WithInsecure()) + } + client = otlptracehttp.NewClient(opts...) + } + exporter := otlptrace.NewUnstarted(client) + startFuncs = append(startFuncs, exporter.Start) + batcherOpt := otelsdktrace.WithBatcher(exporter) + tracerProviderOpts = append(tracerProviderOpts, batcherOpt) + case "stdout": + var opts []stdouttrace.Option + if exporterOpts.File != "" { + file, err := os.OpenFile(exporterOpts.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return nil, fmt.Errorf("failed to open stdout trace file %s: %w", exporterOpts.File, err) + } + opts = append(opts, stdouttrace.WithWriter(file)) + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + return file.Close() + }) + } + if exporterOpts.PrettyPrint { + opts = append(opts, stdouttrace.WithPrettyPrint()) + } + exporter, err := stdouttrace.New(opts...) + if err != nil { + return nil, fmt.Errorf("failed to create stdout trace exporter: %w", err) + } + batcher := otelsdktrace.WithBatcher(exporter) + tracerProviderOpts = append(tracerProviderOpts, batcher) + default: + return nil, fmt.Errorf("unknown trace exporter type: %s", exporterOpts.Type) + } + } + + tracerProvider := otelsdktrace.NewTracerProvider(tracerProviderOpts...) + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + tracerName := cfg.ServiceName + if tracerName == "" { + tracerName = "cosmos-sdk" + } + tracerBase = NewOtelTraceProvider(tracerProvider.Tracer(tracerName)) + + case "metrics": + tracerBase = metricsTraceProvider + default: + tracerBase = log.NewNopTracer() + } + if err != nil { return nil, err } - m := &Metrics{sink: sink, closer: closer} + m := &Metrics{ + sink: sink, + startFuncs: startFuncs, + shutdownFuncs: shutdownFuncs, + traceProvider: tracerBase, + metricsTraceProvider: metricsTraceProvider, + } fanout := metrics.FanoutSink{sink} if cfg.PrometheusRetentionTime > 0 { @@ -351,22 +302,37 @@ func (m *Metrics) gatherGeneric() (GatherResponse, error) { return GatherResponse{ContentType: "application/json", Metrics: content}, nil } -// Close flushes any buffered data and closes resources associated with the metrics system. -// This is primarily needed when using the file sink to ensure all data is written to disk. -// It is safe to call Close() multiple times, and safe to call on a nil Metrics object. -// -// For other sink types (mem, statsd, prometheus), Close() is a no-op. -// -// Example: -// -// m, err := telemetry.New(cfg) -// if err != nil { -// return err -// } -// defer m.Close() -func (m *Metrics) Close() error { - if m == nil || m.closer == nil { - return nil +// TraceProvider returns the base trace provider for creating spans. +func (m *Metrics) TraceProvider() log.TraceProvider { + return m.traceProvider +} + +// MetricsTraceProvider returns a trace provider that only emits metrics for spans. +// Use this when you specifically want to configure a code path to only emit metrics +// and not actual logging spans (useful for benchmarking small operations such as store operations). +func (m *Metrics) MetricsTraceProvider() log.TraceProvider { + return m.traceProvider +} + +// Start starts all configured exporters. +// Start should be called after New() in order to ensure that all configured +// exporters are started. +func (m *Metrics) Start(ctx context.Context) error { + for _, f := range m.startFuncs { + if err := f(ctx); err != nil { + return err + } + } + return nil +} + +// Shutdown must be called before the application exits to shutdown any +// exporters and close any open files. +func (m *Metrics) Shutdown(ctx context.Context) error { + for _, f := range m.shutdownFuncs { + if err := f(ctx); err != nil { + return err + } } - return m.closer.Close() + return nil } diff --git a/telemetry/metrics_span.go b/telemetry/metrics_span.go index 859d004bff9a..48675b4e9af3 100644 --- a/telemetry/metrics_span.go +++ b/telemetry/metrics_span.go @@ -9,6 +9,41 @@ import ( "cosmossdk.io/log" ) +type MetricsTraceProvider struct { + rootPath []string // Base path set at tracer creation, preserved across all spans + rootLabels []metrics.Label // Labels applied to all metrics emitted by this tracer + metrics *metrics.Metrics +} + +func NewMetricsTraceProvider(rootPath []string, rootLabels []metrics.Label, metrics *metrics.Metrics) *MetricsTraceProvider { + return &MetricsTraceProvider{rootPath: rootPath, rootLabels: rootLabels, metrics: metrics} +} + +func (m *MetricsTraceProvider) startSpan(existingPath []string, operation string, _ ...any) log.Span { + path := make([]string, len(existingPath)+1) + copy(path, existingPath) + path[len(path)-1] = operation + return &MetricsSpan{ + MetricsTraceProvider: m, + path: []string{operation}, + start: time.Now(), + } +} + +func (m *MetricsTraceProvider) StartSpan(operation string, _ ...any) log.Span { + return m.startSpan(m.rootPath, operation) +} + +func (m *MetricsTraceProvider) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + return ctx, m.StartSpan(operation, kvs...) +} + +func (m *MetricsTraceProvider) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + return ctx, m.StartSpan(operation, kvs...) +} + +var _ log.TraceProvider = (*MetricsTraceProvider)(nil) + // MetricsSpan is a log.Span implementation that emits timing and count metrics // to go-metrics when the span ends. // @@ -24,11 +59,9 @@ import ( // Root path and labels are preserved across all spans created from this tracer, // ensuring consistent metric namespacing and labeling throughout the span hierarchy. type MetricsSpan struct { - metrics *metrics.Metrics - start time.Time - path []string - rootPath []string // Base path set at tracer creation, preserved across all spans - rootLabels []metrics.Label // Labels applied to all metrics emitted by this tracer + *MetricsTraceProvider + start time.Time + path []string } // NewMetricsTracer creates a new MetricsSpan that acts as a root tracer. @@ -44,11 +77,13 @@ type MetricsSpan struct { // // Emits: "app.tx.validate.time" and "app.tx.validate.count" with module=staking label func NewMetricsTracer(m *metrics.Metrics, rootPath []string, rootLabels []metrics.Label) *MetricsSpan { return &MetricsSpan{ - metrics: m, - path: rootPath, - rootPath: rootPath, - rootLabels: rootLabels, - start: time.Now(), + MetricsTraceProvider: &MetricsTraceProvider{ + metrics: m, + rootPath: rootPath, + rootLabels: rootLabels, + }, + path: rootPath, + start: time.Now(), } } @@ -64,22 +99,13 @@ func (m *MetricsSpan) Impl() any { return nil } // Root path and labels are preserved in the child span. // The kvs parameters are ignored (metrics don't support dynamic attributes). func (m *MetricsSpan) StartSpan(operation string, kvs ...any) log.Span { - path := make([]string, len(m.path)+1) - copy(path, m.path) - path[len(path)-1] = operation - return &MetricsSpan{ - metrics: m.metrics, - path: path, - rootPath: m.rootPath, - rootLabels: m.rootLabels, - start: time.Now(), - } + return m.startSpan(m.path, operation) } // StartSpanContext creates a child span and returns the context unchanged. // The span is not stored in the context. -func (m *MetricsSpan) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - return ctx, m.StartSpan(operation, kvs...) +func (m *MetricsSpan) StartSpanContext(ctx context.Context, operation string, _ ...any) (context.Context, log.Span) { + return ctx, m.startSpan(m.path, operation) } // StartRootSpan creates a new root span by combining the tracer's root path with the operation. @@ -93,30 +119,23 @@ func (m *MetricsSpan) StartSpanContext(ctx context.Context, operation string, kv // defer span.End() // // Emits: "app.process.time" and "app.process.count" func (m *MetricsSpan) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - // Preserve root path and add operation to it - rootPath := append([]string{}, m.rootPath...) - rootPath = append(rootPath, operation) - - return ctx, &MetricsSpan{ - metrics: m.metrics, - path: rootPath, - rootPath: m.rootPath, - rootLabels: m.rootLabels, - start: time.Now(), - } + return ctx, m.startSpan(m.rootPath, operation) } // SetAttrs is a no-op - metrics don't support dynamic attributes. -func (m *MetricsSpan) SetAttrs(kvs ...any) {} +func (m *MetricsSpan) SetAttrs(...any) {} // SetErr is a no-op but returns the error unchanged for convenience. -func (m *MetricsSpan) SetErr(err error, kvs ...any) error { return err } +func (m *MetricsSpan) SetErr(err error, _ ...any) error { return err } // End emits timing and count metrics with ".time" and ".count" suffixes. +// Root labels are applied to both metrics. // // For a span with path ["query", "get"], this emits: // - Timer: "query.get.time" with duration since start // - Counter: "query.get.count" incremented by 1 +// +// If root labels were set (e.g., module=staking), they are included in both metrics. func (m *MetricsSpan) End() { // Create paths with suffixes to avoid metric type conflicts timePath := make([]string, len(m.path)+1) @@ -127,8 +146,14 @@ func (m *MetricsSpan) End() { copy(countPath, m.path) countPath[len(countPath)-1] = "count" - m.metrics.MeasureSince(timePath, m.start) - m.metrics.IncrCounter(countPath, 1) + // Apply root labels to metrics + if len(m.rootLabels) > 0 { + m.metrics.MeasureSinceWithLabels(timePath, m.start, m.rootLabels) + m.metrics.IncrCounterWithLabels(countPath, 1, m.rootLabels) + } else { + m.metrics.MeasureSince(timePath, m.start) + m.metrics.IncrCounter(countPath, 1) + } } var _ log.Span = (*MetricsSpan)(nil) diff --git a/telemetry/metrics_test.go b/telemetry/metrics_test.go index b741f007db22..a0aa4690f81f 100644 --- a/telemetry/metrics_test.go +++ b/telemetry/metrics_test.go @@ -1,7 +1,9 @@ package telemetry import ( + "context" "encoding/json" + "os" "strings" "testing" "time" @@ -57,11 +59,40 @@ func TestMetrics_Prom(t *testing.T) { gr, err := m.Gather(FormatPrometheus) require.NoError(t, err) - require.Equal(t, gr.ContentType, string(ContentTypeText)) + require.Equal(t, gr.ContentType, ContentTypeText) require.True(t, strings.Contains(string(gr.Metrics), "test_dummy_counter 30")) } +func TestMetrics_FileSink(t *testing.T) { + tmpfile := t.TempDir() + "/metrics.jsonl" + + m, err := New(Config{ + MetricsSink: MetricSinkFile, + MetricsFile: tmpfile, + Enabled: true, + EnableHostname: false, + ServiceName: "test", + }) + require.NoError(t, err) + require.NotNil(t, m) + + // Emit a few metrics + metrics.IncrCounter([]string{"test_counter"}, 5.0) + metrics.SetGauge([]string{"test_gauge"}, 42.0) + + // Close to flush buffered data + require.NoError(t, m.Shutdown(context.Background())) + + // Verify file was created and contains metrics + data, err := os.ReadFile(tmpfile) + require.NoError(t, err) + require.NotEmpty(t, data) + require.Contains(t, string(data), `"type":"counter"`) + require.Contains(t, string(data), `"type":"gauge"`) + require.Contains(t, string(data), `"key":["test","test_counter"]`) +} + func emitMetrics() { ticker := time.NewTicker(time.Second) timeout := time.After(30 * time.Second) diff --git a/telemetry/otel_span.go b/telemetry/otel_span.go index b3172d39abb3..4b323105f9cb 100644 --- a/telemetry/otel_span.go +++ b/telemetry/otel_span.go @@ -11,22 +11,52 @@ import ( "cosmossdk.io/log" ) -type OtelSpan struct { - tracer oteltrace.Tracer - ctx context.Context - span oteltrace.Span - persistentAttrs []otelattr.KeyValue +type OtelTracerBase struct { + tracer oteltrace.Tracer } -func NewOtelSpan(tracer oteltrace.Tracer, ctx context.Context, operation string, kvs ...any) (context.Context, *OtelSpan) { - ctx, span := tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...)) - return ctx, &OtelSpan{ +func NewOtelTraceProvider(tracer oteltrace.Tracer) *OtelTracerBase { + return &OtelTracerBase{ tracer: tracer, + } +} + +func (o *OtelTracerBase) StartSpan(operation string, kvs ...any) log.Span { + ctx, span := o.tracer.Start(context.Background(), operation, oteltrace.WithAttributes(toKVs(kvs)...)) + return &OtelSpan{ + tracer: o.tracer, + ctx: ctx, + span: span, + } +} + +func (o *OtelTracerBase) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...)) + return ctx, &OtelSpan{ + tracer: o.tracer, + ctx: ctx, + span: span, + } +} + +func (o *OtelTracerBase) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { + ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...), oteltrace.WithNewRoot()) + return ctx, &OtelSpan{ + tracer: o.tracer, ctx: ctx, span: span, } } +var _ log.TraceProvider = (*OtelTracerBase)(nil) + +type OtelSpan struct { + tracer oteltrace.Tracer + ctx context.Context + span oteltrace.Span + persistentAttrs []otelattr.KeyValue +} + func (o *OtelSpan) addEvent(level, msg string, keyVals ...any) { o.span.AddEvent(msg, oteltrace.WithAttributes(o.persistentAttrs...), From 7fafce361c906a46bbcce0203bffee6b2d88d07f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 12:33:21 -0400 Subject: [PATCH 40/87] fixes --- client/v2/go.mod | 19 ++++++++++++----- client/v2/go.sum | 42 ++++++++++++++++++++++++------------- simapp/go.mod | 25 +++++++++++++--------- simapp/go.sum | 44 +++++++++++++++++++++++++-------------- systemtests/go.mod | 19 ++++++++++++----- systemtests/go.sum | 42 ++++++++++++++++++++++++------------- telemetry/metrics.go | 2 +- telemetry/metrics_span.go | 29 +++----------------------- telemetry/otel_span.go | 14 ++++++------- tests/go.mod | 21 ++++++++++++------- tests/go.sum | 42 ++++++++++++++++++++++++------------- tests/systemtests/go.mod | 19 ++++++++++++----- tests/systemtests/go.sum | 42 ++++++++++++++++++++++++------------- 13 files changed, 222 insertions(+), 138 deletions(-) diff --git a/client/v2/go.mod b/client/v2/go.mod index 6c630e66cc06..7d46f2c4ca34 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -38,6 +38,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect @@ -83,11 +84,13 @@ require ( github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -145,9 +148,15 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -162,8 +171,8 @@ require ( golang.org/x/term v0.36.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.17 // indirect pgregory.net/rapid v1.2.0 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index 2a11b57d4188..58676ed6c45a 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -89,6 +89,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -354,6 +356,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -714,17 +718,27 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -935,10 +949,10 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= diff --git a/simapp/go.mod b/simapp/go.mod index ec751f171381..167548375072 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -72,6 +72,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect @@ -133,6 +134,7 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -201,11 +203,16 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect @@ -222,8 +229,8 @@ require ( golang.org/x/time v0.13.0 // indirect google.golang.org/api v0.247.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/grpc v1.76.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect @@ -233,9 +240,7 @@ require ( sigs.k8s.io/yaml v1.6.0 // indirect ) -replace ( - cosmossdk.io/log => ../log -) +replace cosmossdk.io/log => ../log // Below are the long-lived replace of the SimApp replace ( diff --git a/simapp/go.sum b/simapp/go.sum index 5d1407fd10c2..462724459d82 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -34,8 +34,6 @@ cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= -cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= -cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= @@ -163,6 +161,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -457,6 +457,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow= @@ -845,19 +847,29 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1097,10 +1109,10 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= diff --git a/systemtests/go.mod b/systemtests/go.mod index f8ed7b71fed4..8fd0c16fb18a 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -34,6 +34,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cockroachdb/errors v1.12.0 // indirect @@ -80,11 +81,13 @@ require ( github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -145,9 +148,15 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect @@ -161,8 +170,8 @@ require ( golang.org/x/term v0.36.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index 4cfa4ee4d84e..ae9b36f2df21 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -91,6 +91,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -354,6 +356,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -723,17 +727,27 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -943,10 +957,10 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= diff --git a/telemetry/metrics.go b/telemetry/metrics.go index 405bb3bc7a59..e20ecf600058 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -311,7 +311,7 @@ func (m *Metrics) TraceProvider() log.TraceProvider { // Use this when you specifically want to configure a code path to only emit metrics // and not actual logging spans (useful for benchmarking small operations such as store operations). func (m *Metrics) MetricsTraceProvider() log.TraceProvider { - return m.traceProvider + return m.metricsTraceProvider } // Start starts all configured exporters. diff --git a/telemetry/metrics_span.go b/telemetry/metrics_span.go index 48675b4e9af3..933c5948c323 100644 --- a/telemetry/metrics_span.go +++ b/telemetry/metrics_span.go @@ -25,7 +25,7 @@ func (m *MetricsTraceProvider) startSpan(existingPath []string, operation string path[len(path)-1] = operation return &MetricsSpan{ MetricsTraceProvider: m, - path: []string{operation}, + path: path, start: time.Now(), } } @@ -35,11 +35,11 @@ func (m *MetricsTraceProvider) StartSpan(operation string, _ ...any) log.Span { } func (m *MetricsTraceProvider) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - return ctx, m.StartSpan(operation, kvs...) + return ctx, m.startSpan(m.rootPath, operation) } func (m *MetricsTraceProvider) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - return ctx, m.StartSpan(operation, kvs...) + return ctx, m.startSpan(m.rootPath, operation) } var _ log.TraceProvider = (*MetricsTraceProvider)(nil) @@ -64,29 +64,6 @@ type MetricsSpan struct { path []string } -// NewMetricsTracer creates a new MetricsSpan that acts as a root tracer. -// The rootPath defines the base metric name. If empty, metrics start from the root. -// The rootLabels are applied to all metrics emitted by this tracer and its children. -// -// Example: -// -// labels := []metrics.Label{{Name: "module", Value: "staking"}} -// tracer := NewMetricsTracer(metrics, []string{"app", "tx"}, labels) -// span := tracer.StartSpan("validate") -// defer span.End() -// // Emits: "app.tx.validate.time" and "app.tx.validate.count" with module=staking label -func NewMetricsTracer(m *metrics.Metrics, rootPath []string, rootLabels []metrics.Label) *MetricsSpan { - return &MetricsSpan{ - MetricsTraceProvider: &MetricsTraceProvider{ - metrics: m, - rootPath: rootPath, - rootLabels: rootLabels, - }, - path: rootPath, - start: time.Now(), - } -} - // Logger methods are no-ops - MetricsSpan does not support logging. func (m *MetricsSpan) Info(msg string, keyVals ...any) {} func (m *MetricsSpan) Warn(msg string, keyVals ...any) {} diff --git a/telemetry/otel_span.go b/telemetry/otel_span.go index 4b323105f9cb..e7c6aea9231f 100644 --- a/telemetry/otel_span.go +++ b/telemetry/otel_span.go @@ -11,17 +11,17 @@ import ( "cosmossdk.io/log" ) -type OtelTracerBase struct { +type OtelTraceProvider struct { tracer oteltrace.Tracer } -func NewOtelTraceProvider(tracer oteltrace.Tracer) *OtelTracerBase { - return &OtelTracerBase{ +func NewOtelTraceProvider(tracer oteltrace.Tracer) *OtelTraceProvider { + return &OtelTraceProvider{ tracer: tracer, } } -func (o *OtelTracerBase) StartSpan(operation string, kvs ...any) log.Span { +func (o *OtelTraceProvider) StartSpan(operation string, kvs ...any) log.Span { ctx, span := o.tracer.Start(context.Background(), operation, oteltrace.WithAttributes(toKVs(kvs)...)) return &OtelSpan{ tracer: o.tracer, @@ -30,7 +30,7 @@ func (o *OtelTracerBase) StartSpan(operation string, kvs ...any) log.Span { } } -func (o *OtelTracerBase) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { +func (o *OtelTraceProvider) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...)) return ctx, &OtelSpan{ tracer: o.tracer, @@ -39,7 +39,7 @@ func (o *OtelTracerBase) StartSpanContext(ctx context.Context, operation string, } } -func (o *OtelTracerBase) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { +func (o *OtelTraceProvider) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...), oteltrace.WithNewRoot()) return ctx, &OtelSpan{ tracer: o.tracer, @@ -48,7 +48,7 @@ func (o *OtelTracerBase) StartRootSpan(ctx context.Context, operation string, kv } } -var _ log.TraceProvider = (*OtelTracerBase)(nil) +var _ log.TraceProvider = (*OtelTraceProvider)(nil) type OtelSpan struct { tracer oteltrace.Tracer diff --git a/tests/go.mod b/tests/go.mod index 8c13fa6ff2f4..fff27db9c449 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -75,6 +75,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudwego/base64x v0.1.6 // indirect @@ -132,6 +133,7 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -201,11 +203,16 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect @@ -222,8 +229,8 @@ require ( golang.org/x/time v0.13.0 // indirect google.golang.org/api v0.247.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.17 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index 416fb05e6ede..b7404e7f61e8 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -161,6 +161,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -452,6 +454,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow= @@ -844,19 +848,29 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1100,10 +1114,10 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index d91bf94d9312..7c9b858c29da 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -38,6 +38,7 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cockroachdb/errors v1.12.0 // indirect @@ -86,11 +87,13 @@ require ( github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -151,9 +154,15 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect @@ -167,8 +176,8 @@ require ( golang.org/x/term v0.36.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index 90428e129f04..f3ec451282a3 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -89,6 +89,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -352,6 +354,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -721,17 +725,27 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -941,10 +955,10 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= From 7bd8e2132a03eb481378a05ecfd6d50c896ada02 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:33:10 -0700 Subject: [PATCH 41/87] feat(iavl): blockSTM updates to iavlx store impl (#25504) --- client/v2/go.mod | 2 ++ client/v2/go.sum | 3 ++ iavl/commit_multi_tree.go | 51 ++++++++++++++++++++++------ iavl/commit_tree.go | 16 ++++++--- iavl/multi_tree.go | 70 ++++++++++++++++++++++++++++++--------- iavl/tree.go | 10 +++--- iavl/tree_store.go | 7 +++- iavl/tree_test.go | 63 +++++++++++++++++++++++++++++++++++ systemtests/go.mod | 2 ++ systemtests/go.sum | 3 ++ 10 files changed, 190 insertions(+), 37 deletions(-) diff --git a/client/v2/go.mod b/client/v2/go.mod index c67270892c80..f1eaaf38fcf7 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -32,6 +32,7 @@ require ( github.com/99designs/keyring v1.2.1 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect + github.com/alitto/pond/v2 v2.5.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect @@ -62,6 +63,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index cdc51cec2ae5..7389c0045c33 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -50,6 +50,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= +github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -199,6 +201,7 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/iavl/commit_multi_tree.go b/iavl/commit_multi_tree.go index f04077c77158..6e7e6e98af28 100644 --- a/iavl/commit_multi_tree.go +++ b/iavl/commit_multi_tree.go @@ -25,7 +25,7 @@ type CommitMultiTree struct { dir string opts Options logger log.Logger - trees []storetypes.CommitKVStore // always ordered by tree name + trees []storetypes.CommitStore // always ordered by tree name treeKeys []storetypes.StoreKey // always ordered by tree name storeTypes []storetypes.StoreType // store types by tree index treesByKey map[storetypes.StoreKey]int // index of the trees by name @@ -37,6 +37,24 @@ type CommitMultiTree struct { workingHash []byte } +// GetObjKVStore returns a mounted ObjKVStore for a given StoreKey. +func (db *CommitMultiTree) GetObjKVStore(key storetypes.StoreKey) storetypes.ObjKVStore { + treeIdx, ok := db.treesByKey[key] + if !ok { + panic(fmt.Sprintf("tree key not found in treesByKey: %v", key)) + } + s := db.trees[treeIdx] + if s == nil { + panic(fmt.Sprintf("store does not exist for key: %s", key.Name())) + } + store, ok := s.(storetypes.ObjKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not ObjKVStore", key)) + } + + return store +} + func (db *CommitMultiTree) LastCommitID() storetypes.CommitID { return db.lastCommitId } @@ -157,11 +175,11 @@ func (db *CommitMultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceCo func (db *CommitMultiTree) CacheMultiStore() storetypes.CacheMultiStore { mt := &MultiTree{ - trees: make([]storetypes.CacheKVStore, len(db.trees)), + trees: make([]storetypes.CacheWrap, len(db.trees)), treesByKey: db.treesByKey, // share the map } for i, tree := range db.trees { - mt.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) + mt.trees[i] = tree.CacheWrap() } return mt } @@ -174,7 +192,7 @@ func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes mt := &MultiTree{ latestVersion: version, treesByKey: db.treesByKey, // share the map - trees: make([]storetypes.CacheKVStore, len(db.trees)), + trees: make([]storetypes.CacheWrap, len(db.trees)), } for i, tree := range db.trees { @@ -186,10 +204,8 @@ func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes if err != nil { return nil, fmt.Errorf("failed to create cache multi store for tree %s at version %d: %w", db.treeKeys[i].Name(), version, err) } - case storetypes.StoreTypeTransient, storetypes.StoreTypeMemory: - mt.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) default: - return nil, fmt.Errorf("unsupported store type: %s", typ.String()) + mt.trees[i] = tree.CacheWrap() } } @@ -208,7 +224,12 @@ func (db *CommitMultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStor if index >= len(db.trees) { panic(fmt.Sprintf("store index %d out of bounds for key %s (trees length: %d)", index, key.Name(), len(db.trees))) } - return db.trees[index] + s := db.trees[index] + store, ok := s.(storetypes.KVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not KVStore", key)) + } + return store } func (db *CommitMultiTree) TracingEnabled() bool { @@ -256,7 +277,12 @@ func (db *CommitMultiTree) GetCommitStore(key storetypes.StoreKey) storetypes.Co } func (db *CommitMultiTree) GetCommitKVStore(key storetypes.StoreKey) storetypes.CommitKVStore { - return db.trees[db.treesByKey[key]] + s := db.trees[db.treesByKey[key]] + store, ok := s.(storetypes.CommitKVStore) + if !ok { + panic(fmt.Sprintf("store with key %s is not CommitKVStore", key.Name())) + } + return store } func (db *CommitMultiTree) LoadLatestVersion() error { @@ -271,7 +297,7 @@ func (db *CommitMultiTree) LoadLatestVersion() error { return nil } -func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.StoreType) (storetypes.CommitKVStore, error) { +func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.StoreType) (storetypes.CommitStore, error) { switch typ { case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: dir := filepath.Join(db.dir, key.Name()) @@ -296,6 +322,11 @@ func (db *CommitMultiTree) loadStore(key storetypes.StoreKey, typ storetypes.Sto } return mem.NewStore(), nil + case storetypes.StoreTypeObject: + if _, ok := key.(*storetypes.ObjectStoreKey); !ok { + return nil, fmt.Errorf("unexpected key type for a ObjectStoreKey: %s", key.String()) + } + return transient.NewObjStore(), nil default: return nil, fmt.Errorf("unsupported store type: %s", typ.String()) } diff --git a/iavl/commit_tree.go b/iavl/commit_tree.go index b3c7151238ee..d5939354f10a 100644 --- a/iavl/commit_tree.go +++ b/iavl/commit_tree.go @@ -209,13 +209,19 @@ func (c *CommitTree) Has(key []byte) bool { } func (c *CommitTree) Set(key, value []byte) { - tree := c.CacheWrap().(*Tree) + tree, ok := c.CacheWrap().(*Tree) + if !ok { + panic("CommitTree could not cast to Tree for Set") + } tree.Set(key, value) tree.Write() } func (c *CommitTree) Delete(key []byte) { - tree := c.CacheWrap().(*Tree) + tree, ok := c.CacheWrap().(*Tree) + if !ok { + panic("CommitTree could not cast to Tree for Delete") + } tree.Delete(key) tree.Write() } @@ -301,7 +307,7 @@ func (c *CommitTree) startEvict(evictVersion uint32) { }() } -func (c *CommitTree) GetImmutable(version int64) (storetypes.CacheKVStore, error) { +func (c *CommitTree) GetImmutable(version int64) (storetypes.CacheWrap, error) { var rootPtr *NodePointer if version == c.lastCommitId.Version { rootPtr = c.root @@ -400,6 +406,6 @@ func evictTraverse(np *NodePointer, depth, evictionDepth uint8, evictVersion uin } var ( - _ storetypes.CommitKVStore = &CommitTree{} - _ parentTree = &CommitTree{} + _ storetypes.CommitStore = &CommitTree{} + _ parentTree = &CommitTree{} ) diff --git a/iavl/multi_tree.go b/iavl/multi_tree.go index dfe4858a6785..6e94d2b2b587 100644 --- a/iavl/multi_tree.go +++ b/iavl/multi_tree.go @@ -9,8 +9,41 @@ import ( type MultiTree struct { latestVersion int64 - trees []storetypes.CacheKVStore // always ordered by tree name + trees []storetypes.CacheWrap // always ordered by tree name treesByKey map[storetypes.StoreKey]int // index of the trees by name + + parentTree func(storetypes.StoreKey) storetypes.CacheWrapper +} + +func (t *MultiTree) GetObjKVStore(key storetypes.StoreKey) storetypes.ObjKVStore { + store, ok := t.getCacheWrapper(key).(storetypes.ObjKVStore) + if !ok { + panic(fmt.Sprintf("store with key %v is not ObjKVStore", key)) + } + return store +} + +func (t *MultiTree) getCacheWrapper(key storetypes.StoreKey) storetypes.CacheWrapper { + var store storetypes.CacheWrapper + treeIdx, ok := t.treesByKey[key] + if !ok { + if t.parentTree != nil { + store = t.initStore(key, t.parentTree(key)) + } else { + panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) + } + } else { + store = t.trees[treeIdx] + } + + return store +} + +func (t *MultiTree) initStore(key storetypes.StoreKey, store storetypes.CacheWrapper) storetypes.CacheWrap { + cache := store.CacheWrap() + t.trees = append(t.trees, cache) + t.treesByKey[key] = len(t.trees) - 1 + return cache } func (t *MultiTree) Write() { @@ -33,14 +66,16 @@ func (t *MultiTree) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) } func (t *MultiTree) CacheMultiStore() storetypes.CacheMultiStore { - wrapped := &MultiTree{ - treesByKey: t.treesByKey, - trees: make([]storetypes.CacheKVStore, len(t.trees)), - } - for i, tree := range t.trees { - wrapped.trees[i] = tree.CacheWrap().(storetypes.CacheKVStore) + return NewFromParent(t.getCacheWrapper, t.latestVersion) +} + +func NewFromParent(parentStore func(storetypes.StoreKey) storetypes.CacheWrapper, version int64) *MultiTree { + return &MultiTree{ + latestVersion: version, + parentTree: parentStore, + trees: make([]storetypes.CacheWrap, 0), + treesByKey: make(map[storetypes.StoreKey]int), } - return wrapped } func (t *MultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { @@ -48,18 +83,21 @@ func (t *MultiTree) CacheMultiStoreWithVersion(version int64) (storetypes.CacheM } func (t *MultiTree) GetStore(key storetypes.StoreKey) storetypes.Store { - return t.trees[t.treesByKey[key]] + store, ok := t.getCacheWrapper(key).(storetypes.Store) + if !ok { + panic(fmt.Sprintf("store with key %v is not Store", key)) + } + return store } func (t *MultiTree) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { - index, ok := t.treesByKey[key] + store := t.getCacheWrapper(key) + + kvStore, ok := store.(storetypes.KVStore) if !ok { - panic(fmt.Sprintf("store not found for key: %s (key type: %T)", key.Name(), key)) - } - if index >= len(t.trees) { - panic(fmt.Sprintf("store index %d out of bounds for key %s (trees length: %d)", index, key.Name(), len(t.trees))) + panic(fmt.Sprintf("store with key %v is not KVStore: store type=%T", key, store)) } - return t.trees[index] + return kvStore } func (t *MultiTree) TracingEnabled() bool { @@ -80,4 +118,4 @@ func (t *MultiTree) LatestVersion() int64 { return t.latestVersion } -var _ storetypes.CacheMultiStore = &MultiTree{} +var _ storetypes.MultiStore = &MultiTree{} diff --git a/iavl/tree.go b/iavl/tree.go index 8dc3c0f6118e..7b359003d2d7 100644 --- a/iavl/tree.go +++ b/iavl/tree.go @@ -3,7 +3,6 @@ package iavlx import ( io "io" - corestore "cosmossdk.io/core/store" storetypes "cosmossdk.io/store/types" ) @@ -130,15 +129,16 @@ func (tree *Tree) Has(key []byte) bool { return val != nil } -func (tree *Tree) Iterator(start, end []byte) corestore.Iterator { +func (tree *Tree) Iterator(start, end []byte) storetypes.Iterator { return NewIterator(start, end, true, tree.root, tree.zeroCopy) } -func (tree *Tree) ReverseIterator(start, end []byte) corestore.Iterator { +func (tree *Tree) ReverseIterator(start, end []byte) storetypes.Iterator { return NewIterator(start, end, false, tree.root, tree.zeroCopy) } var ( - _ storetypes.CacheKVStore = (*Tree)(nil) - _ parentTree = (*Tree)(nil) + _ storetypes.CacheWrap = (*Tree)(nil) + _ storetypes.KVStore = (*Tree)(nil) + _ parentTree = (*Tree)(nil) ) diff --git a/iavl/tree_store.go b/iavl/tree_store.go index b2c1dcda19fa..9024033594cd 100644 --- a/iavl/tree_store.go +++ b/iavl/tree_store.go @@ -89,7 +89,12 @@ func (ts *TreeStore) getChangesetEntryForVersion(version uint32) *changesetEntry } func (ts *TreeStore) getChangesetForVersion(version uint32) *Changeset { - return ts.getChangesetEntryForVersion(version).changeset.Load() + cs := ts.getChangesetEntryForVersion(version) + if cs == nil { + return nil + } else { + return cs.changeset.Load() + } } func (ts *TreeStore) ReadK(nodeId NodeID, _ uint32) (key []byte, err error) { diff --git a/iavl/tree_test.go b/iavl/tree_test.go index 0bb57e2df022..6cca6537c070 100644 --- a/iavl/tree_test.go +++ b/iavl/tree_test.go @@ -17,6 +17,69 @@ import ( sdklog "cosmossdk.io/log" ) +// TODO: this test isn't for expected behavior. +// It should eventually be updated such that having a default ReaderUpdateInterval shouldn't error on old version queries. +func TestTree_ErrorsOnOldVersion(t *testing.T) { + testCases := []struct { + name string + getTree func() *CommitTree + expError error + }{ + { + name: "should error", + getTree: func() *CommitTree { + dir := t.TempDir() + commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger()) + require.NoError(t, err) + return commitTree + }, + // TODO: this shouldn't error! + expError: fmt.Errorf("no changeset found for version 2"), + }, + { + name: "should NOT error", + getTree: func() *CommitTree { + dir := t.TempDir() + commitTree, err := NewCommitTree(dir, Options{ReaderUpdateInterval: 1}, sdklog.NewNopLogger()) + require.NoError(t, err) + return commitTree + }, + expError: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + commitTree := tc.getTree() + for range 7 { + tree := commitTree.CacheWrap().(*Tree) + tree.Set([]byte{0}, []byte{1}) + tree.Write() + commitTree.Commit() + } + _, err := commitTree.GetImmutable(2) + require.Equal(t, tc.expError, err) + }) + } + +} + +func TestTree_NonExistentChangeset(t *testing.T) { + dir := t.TempDir() + commitTree, err := NewCommitTree(dir, Options{ReaderUpdateInterval: 1}, sdklog.NewNopLogger()) + require.NoError(t, err) + + for range 7 { + tree := commitTree.CacheWrap().(*Tree) + tree.Set([]byte{0}, []byte{1}) + tree.Write() + commitTree.Commit() + } + + _, err = commitTree.GetImmutable(2) + require.NoError(t, err) +} + func TestBasicTest(t *testing.T) { dir, err := os.MkdirTemp("", "iavlx") require.NoError(t, err) diff --git a/systemtests/go.mod b/systemtests/go.mod index 331eec734035..74c33c35c9bc 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -28,6 +28,7 @@ require ( github.com/99designs/keyring v1.2.1 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect + github.com/alitto/pond/v2 v2.5.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect @@ -59,6 +60,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index f5623a40d519..c36a7d9984a1 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -52,6 +52,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= +github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -199,6 +201,7 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= From bdba035644d3924b9af2fd3b345c8610b9da8761 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 13:45:26 -0400 Subject: [PATCH 42/87] simapp setup, make tracers wrap loggers --- baseapp/baseapp.go | 4 +- baseapp/test_helpers.go | 8 ++-- log/tracer.go | 34 ++++++++-------- simapp/app_di.go | 41 ++++++++++++++++++- telemetry/config.go | 38 ++++++++--------- telemetry/metrics.go | 86 ++++++++++++++++++--------------------- telemetry/metrics_span.go | 42 ++++++++++--------- telemetry/options.go | 14 +++++++ telemetry/otel_span.go | 19 +++++---- 9 files changed, 169 insertions(+), 117 deletions(-) create mode 100644 telemetry/options.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 14b42770af07..9de5a9dcaff4 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -191,7 +191,7 @@ func NewBaseApp( // initialize tracer tracer, ok := app.logger.(log.Tracer) if !ok { - tracer = log.NewNopTracer() + tracer = log.NewNopTracer(app.logger) } app.tracer = tracer @@ -992,7 +992,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me // extract the tracer from the context tracer, ok := ctx.Logger().(log.Tracer) if !ok { - tracer = log.NewNopTracer() + tracer = log.NewNopTracer(app.logger) } span := tracer.StartSpan("runMsgs") defer span.End() diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index 560f96e61834..12695792d659 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -20,13 +20,13 @@ func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, * return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeCheck, bz, tx, -1, nil, nil, log.NewNopTracer()) + gasInfo, result, _, err := app.RunTx(execModeCheck, bz, tx, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) return gasInfo, result, err } // Simulate executes a tx in simulate mode to get result and gas info. func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { - gasInfo, result, _, err := app.RunTx(execModeSimulate, txBytes, nil, -1, nil, nil, log.NewNopTracer()) + gasInfo, result, _, err := app.RunTx(execModeSimulate, txBytes, nil, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) return gasInfo, result, err } @@ -37,7 +37,7 @@ func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer()) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) return gasInfo, result, err } @@ -48,7 +48,7 @@ func (app *BaseApp) SimTxFinalizeBlock(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk. return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer()) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) return gasInfo, result, err } diff --git a/log/tracer.go b/log/tracer.go index 00274bd2414a..ac304b1a5e41 100644 --- a/log/tracer.go +++ b/log/tracer.go @@ -2,8 +2,13 @@ package log import "context" -// TraceProvider is an interface for creating and managing tracing spans. -type TraceProvider interface { +// Tracer is an interface for creating and managing spans. +// It may be backed by open telemetry or other tracing libraries, +// Spans may also be used for collecting timing metrics. +// It embeds the Logger interface. Log events may be associated with spans. +type Tracer interface { + Logger + // StartSpan starts a new span with the given operation name and key-value pair attributes. // If there is a parent span, the new span will be a child of that span. // It is recommended to use a defer statement to end the span like this: @@ -29,15 +34,6 @@ type TraceProvider interface { StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, Span) } -// Tracer is an interface for creating and managing spans. -// It may be backed by open telemetry or other tracing libraries, -// Spans may also be used for collecting timing metrics. -// It embeds the Logger interface. Log events may be associated with spans. -type Tracer interface { - Logger - TraceProvider -} - // Span is an interface for managing spans and creating nested spans via the embedded Tracer interface. type Span interface { // Tracer is embedded to allow for the creation of nested spans. @@ -66,25 +62,27 @@ type Span interface { End() } -// NewNopTracer returns a Tracer that does nothing. -func NewNopTracer() Tracer { - return nopTracer{} +// NewNopTracer returns a Tracer that wraps a logger for logging methods but does not emit any spans. +func NewNopTracer(logger Logger) Tracer { + return nopTracer{ + Logger: logger, + } } type nopTracer struct { - nopLogger + Logger } func (n nopTracer) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, Span) { - return ctx, nopSpan{} + return ctx, nopSpan{n} } func (n nopTracer) StartSpanContext(ctx context.Context, _ string, _ ...any) (context.Context, Span) { - return ctx, nopSpan{} + return ctx, nopSpan{n} } func (n nopTracer) StartSpan(string, ...any) Span { - return nopSpan{} + return nopSpan{n} } type nopSpan struct { diff --git a/simapp/app_di.go b/simapp/app_di.go index 3e8e010d0acb..d9516de47564 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -3,15 +3,23 @@ package simapp import ( + "context" + "encoding/json" + "fmt" "io" + "os" + "os/signal" + "syscall" dbm "github.com/cosmos/cosmos-db" clienthelpers "cosmossdk.io/client/v2/helpers" "cosmossdk.io/depinject" - "cosmossdk.io/log" storetypes "cosmossdk.io/store/types" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/telemetry" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -100,6 +108,37 @@ func NewSimApp( appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { + // configure metrics and tracing for testing + telemetryCfgJson, ok := os.LookupEnv("SIMAPP_TELEMETRY") + if ok && telemetryCfgJson != "" { + var telemetryCfg telemetry.Config + err := json.Unmarshal([]byte(telemetryCfgJson), &telemetryCfg) + if err != nil { + panic(fmt.Errorf("failed to parse simapp telemetry config: %v\n", err)) + } else { + fmt.Printf("\nConfiguring telemetry for simapp: %+v\n", telemetryCfg) + metrics, err := telemetry.New(telemetryCfg, telemetry.WithLogger(logger)) + if err != nil { + panic(fmt.Errorf("failed to initialize simapp telemetry: %v\n", err)) + } + logger = metrics.Tracer() + err = metrics.Start(context.Background()) + if err != nil { + panic(fmt.Errorf("failed to start simapp telemetry: %v\n", err)) + } + shutdownChan := make(chan os.Signal, 1) + signal.Notify(shutdownChan, os.Interrupt, syscall.SIGTERM) + go func() { + // shutdown when there's a shutdown signal + <-shutdownChan + err := metrics.Shutdown(context.Background()) + if err != nil { + fmt.Printf("failed to shutdown simapp telemetry: %v\n", err) + } + }() + } + } + var ( app = &SimApp{} appBuilder *runtime.AppBuilder diff --git a/telemetry/config.go b/telemetry/config.go index e707a127251d..1e9eeb52ac97 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -50,29 +50,29 @@ type DisplayableSink interface { type Config struct { // ServiceName is the identifier for this service, used as a prefix for all metric keys. // Example: "cosmos-app" → metrics like "cosmos-app.tx.count" - ServiceName string `mapstructure:"service-name"` + ServiceName string `mapstructure:"service-name" json:"service-name"` // Enabled controls whether telemetry is active. When false, all telemetry operations // become no-ops with zero overhead. When true, metrics collection is activated. - Enabled bool `mapstructure:"enabled"` + Enabled bool `mapstructure:"enabled" json:"enabled"` // EnableHostname prefixes gauge values with the hostname. // Useful in multi-node deployments to identify which node emitted a metric. - EnableHostname bool `mapstructure:"enable-hostname"` + EnableHostname bool `mapstructure:"enable-hostname" json:"enable-hostname"` // EnableHostnameLabel adds a "hostname" label to all metrics. // Alternative to EnableHostname that works better with label-based systems like Prometheus. - EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` + EnableHostnameLabel bool `mapstructure:"enable-hostname-label" json:"enable-hostname-label"` // EnableServiceLabel adds a "service" label with the ServiceName to all metrics. // Useful when aggregating metrics from multiple services in one monitoring system. - EnableServiceLabel bool `mapstructure:"enable-service-label"` + EnableServiceLabel bool `mapstructure:"enable-service-label" json:"enable-service-label"` // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. // Defines how long (in seconds) metrics are retained in memory for scraping. // The Prometheus sink is added to a FanoutSink alongside the primary sink. // Recommended value: 60 seconds or more. - PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` + PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time" json:"prometheus-retention-time"` // GlobalLabels defines a set of key-value label pairs applied to ALL metrics. // These labels are automatically attached to every metric emission. @@ -81,7 +81,7 @@ type Config struct { // Example: [][]string{{"chain_id", "cosmoshub-1"}, {"env", "production"}} // // Note: The outer array contains label pairs, each inner array has exactly 2 elements [key, value]. - GlobalLabels [][]string `mapstructure:"global-labels"` + GlobalLabels [][]string `mapstructure:"global-labels" json:"global-labels"` // MetricsSink defines the metrics backend type. Supported values: // - "mem" (default): In-memory sink with SIGUSR1 dump-to-stderr capability @@ -91,33 +91,33 @@ type Config struct { // - "file": JSON lines written to a file (requires MetricsFile) // // Multiple sinks can be active via FanoutSink (e.g., mem + prometheus). - MetricsSink string `mapstructure:"metrics-sink" default:"mem"` + MetricsSink string `mapstructure:"metrics-sink" json:"metrics-sink" default:"mem"` // StatsdAddr is the address of the StatsD or DogStatsD server (host:port). // Only used when MetricsSink is "statsd" or "dogstatsd". // Example: "localhost:8125" - StatsdAddr string `mapstructure:"statsd-addr"` + StatsdAddr string `mapstructure:"statsd-addr" json:"statsd-addr"` // DatadogHostname is the hostname to report when using DogStatsD. // Only used when MetricsSink is "dogstatsd". // If empty, the system hostname is used. - DatadogHostname string `mapstructure:"datadog-hostname"` + DatadogHostname string `mapstructure:"datadog-hostname" json:"datadog-hostname"` // MetricsFile is the file path to write metrics to in JSONL format. // Only used when MetricsSink is "file". // Each metric emission creates a JSON line: {"timestamp":"...","type":"counter","key":[...],"value":1.0} // Example: "/tmp/metrics.jsonl" or "./metrics.jsonl" - MetricsFile string `mapstructure:"metrics-file"` + MetricsFile string `mapstructure:"metrics-file" json:"metrics-file"` // TraceSink is the sink for trace data. Supported values: // - "otel": OpenTelemetry trace sink // - "metrics": all spans will be redirected to emit invocation counter and timing histogram metrics // - "noop": No-op trace sink (default) - TraceSink string `mapstructure:"trace-sink"` + TraceSink string `mapstructure:"trace-sink" json:"trace-sink"` // OtelTraceExporters is a list of OTLP exporters to use for trace data. // This is only used when trace sink is set to "otel". - OtelTraceExporters []OtelTraceExportConfig `mapstructure:"otel-trace-exporters"` + OtelTraceExporters []OtelTraceExportConfig `mapstructure:"otel-trace-exporters" json:"otel-trace-exporters"` } type OtelTraceExportConfig struct { @@ -127,24 +127,24 @@ type OtelTraceExportConfig struct { // - "otlp" // // OTLP exporters must set the endpoint URL and can optionally set the transport protocol. - Type string `mapstructure:"type"` + Type string `mapstructure:"type" json:"type"` // OTLPTransport is the transport protocol to use for OTLP. // Must be one of: // - "http" (default) // - "grpc" - OTLPTransport string `mapstructure:"otlp-transport"` + OTLPTransport string `mapstructure:"otlp-transport" json:"otlp-transport"` // Endpoint is the OTLP exporter endpoint URL (grpc or http). - Endpoint string `mapstructure:"endpoint"` + Endpoint string `mapstructure:"endpoint" json:"endpoint"` // Insecure disables TLS certificate verification for OTLP exporters. - Insecure bool `mapstructure:"insecure"` + Insecure bool `mapstructure:"insecure" json:"insecure"` // File is the file path to write trace data to when using the "stdout" exporter. // If it is empty, the trace data is written to stdout. - File string `mapstructure:"file"` + File string `mapstructure:"file" json:"file"` // PrettyPrint enables pretty-printing of JSON output when using the "stdout" exporter. - PrettyPrint bool `mapstructure:"pretty-print"` + PrettyPrint bool `mapstructure:"pretty-print" json:"pretty-print"` } diff --git a/telemetry/metrics.go b/telemetry/metrics.go index e20ecf600058..3bcb777918bb 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -37,12 +37,13 @@ import ( // Note: go-metrics uses a singleton global registry. Only one Metrics instance // should be created per process. type Metrics struct { - sink metrics.MetricSink - prometheusEnabled bool - startFuncs []func(ctx context.Context) error - shutdownFuncs []func(context.Context) error - traceProvider log.TraceProvider - metricsTraceProvider *MetricsTraceProvider + sink metrics.MetricSink + prometheusEnabled bool + startFuncs []func(ctx context.Context) error + shutdownFuncs []func(context.Context) error + logger log.Logger + tracer log.Tracer + metricsTracer *MetricsTracer } // GatherResponse contains collected metrics in the requested format. @@ -77,7 +78,7 @@ type GatherResponse struct { // return err // } // defer m.Close() -func New(cfg Config) (_ *Metrics, rerr error) { +func New(cfg Config, opts ...Option) (_ *Metrics, rerr error) { globalTelemetryEnabled = cfg.Enabled if !cfg.Enabled { return nil, nil @@ -95,18 +96,21 @@ func New(cfg Config) (_ *Metrics, rerr error) { metricsConf.EnableHostname = cfg.EnableHostname metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel - var startFuncs []func(context.Context) error - var shutdownFuncs []func(context.Context) error + // Initialize Metrics struct early with default logger + m := &Metrics{ + logger: log.NewNopLogger(), // Default to nop logger + } + // Apply functional options to set logger + for _, opt := range opts { + opt(m) + } - var ( - sink metrics.MetricSink - err error - ) + var err error switch cfg.MetricsSink { case MetricSinkStatsd: - sink, err = metrics.NewStatsdSink(cfg.StatsdAddr) + m.sink, err = metrics.NewStatsdSink(cfg.StatsdAddr) case MetricSinkDogsStatsd: - sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname) + m.sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname) case MetricSinkFile: if cfg.MetricsFile == "" { return nil, errors.New("metrics-file must be set when metrics-sink is 'file'") @@ -116,13 +120,13 @@ func New(cfg Config) (_ *Metrics, rerr error) { return nil, fmt.Errorf("failed to open metrics file: %w", err) } fileSink := NewFileSink(file) - sink = fileSink - shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { - return fileSink.Close() + m.shutdownFuncs = append(m.shutdownFuncs, func(ctx context.Context) error { + return file.Close() }) + m.sink = fileSink default: memSink := metrics.NewInmemSink(10*time.Second, time.Minute) - sink = memSink + m.sink = memSink inMemSig := metrics.DefaultInmemSignal(memSink) defer func() { if rerr != nil { @@ -130,10 +134,13 @@ func New(cfg Config) (_ *Metrics, rerr error) { } }() } + if err != nil { + return nil, err + } - metricsTraceProvider := NewMetricsTraceProvider(nil, globalLabels, metrics.Default()) + m.tracer = log.NewNopTracer(m.logger) + m.metricsTracer = NewMetricsTracer(metrics.Default(), nil, globalLabels, m.logger) - var tracerBase log.TraceProvider switch cfg.TraceSink { case TraceSinkOtel: var tracerProviderOpts []otelsdktrace.TracerProviderOption @@ -160,7 +167,7 @@ func New(cfg Config) (_ *Metrics, rerr error) { client = otlptracehttp.NewClient(opts...) } exporter := otlptrace.NewUnstarted(client) - startFuncs = append(startFuncs, exporter.Start) + m.startFuncs = append(m.startFuncs, exporter.Start) batcherOpt := otelsdktrace.WithBatcher(exporter) tracerProviderOpts = append(tracerProviderOpts, batcherOpt) case "stdout": @@ -171,7 +178,7 @@ func New(cfg Config) (_ *Metrics, rerr error) { return nil, fmt.Errorf("failed to open stdout trace file %s: %w", exporterOpts.File, err) } opts = append(opts, stdouttrace.WithWriter(file)) - shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + m.shutdownFuncs = append(m.shutdownFuncs, func(ctx context.Context) error { return file.Close() }) } @@ -190,31 +197,18 @@ func New(cfg Config) (_ *Metrics, rerr error) { } tracerProvider := otelsdktrace.NewTracerProvider(tracerProviderOpts...) - shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + m.shutdownFuncs = append(m.shutdownFuncs, tracerProvider.Shutdown) tracerName := cfg.ServiceName if tracerName == "" { tracerName = "cosmos-sdk" } - tracerBase = NewOtelTraceProvider(tracerProvider.Tracer(tracerName)) - + m.tracer = NewOtelTracer(tracerProvider.Tracer(tracerName), m.logger) case "metrics": - tracerBase = metricsTraceProvider + m.tracer = m.metricsTracer default: - tracerBase = log.NewNopTracer() } - if err != nil { - return nil, err - } - - m := &Metrics{ - sink: sink, - startFuncs: startFuncs, - shutdownFuncs: shutdownFuncs, - traceProvider: tracerBase, - metricsTraceProvider: metricsTraceProvider, - } - fanout := metrics.FanoutSink{sink} + fanout := metrics.FanoutSink{m.sink} if cfg.PrometheusRetentionTime > 0 { m.prometheusEnabled = true @@ -302,16 +296,16 @@ func (m *Metrics) gatherGeneric() (GatherResponse, error) { return GatherResponse{ContentType: "application/json", Metrics: content}, nil } -// TraceProvider returns the base trace provider for creating spans. -func (m *Metrics) TraceProvider() log.TraceProvider { - return m.traceProvider +// Tracer returns the base tracer for creating spans and logging. +func (m *Metrics) Tracer() log.Tracer { + return m.tracer } -// MetricsTraceProvider returns a trace provider that only emits metrics for spans. +// MetricsTracer returns a tracer that only emits metrics for spans. // Use this when you specifically want to configure a code path to only emit metrics // and not actual logging spans (useful for benchmarking small operations such as store operations). -func (m *Metrics) MetricsTraceProvider() log.TraceProvider { - return m.metricsTraceProvider +func (m *Metrics) MetricsTracer() log.Tracer { + return m.metricsTracer } // Start starts all configured exporters. diff --git a/telemetry/metrics_span.go b/telemetry/metrics_span.go index 933c5948c323..407c91708ae1 100644 --- a/telemetry/metrics_span.go +++ b/telemetry/metrics_span.go @@ -9,40 +9,50 @@ import ( "cosmossdk.io/log" ) -type MetricsTraceProvider struct { +// MetricsTracer is a log.Tracer implementation that emits metrics for span operations. +// It wraps a logger and forwards all log calls to it, while creating MetricsSpan instances +// that emit timing and count metrics. +type MetricsTracer struct { + log.Logger // Logger to forward log calls to rootPath []string // Base path set at tracer creation, preserved across all spans rootLabels []metrics.Label // Labels applied to all metrics emitted by this tracer metrics *metrics.Metrics } -func NewMetricsTraceProvider(rootPath []string, rootLabels []metrics.Label, metrics *metrics.Metrics) *MetricsTraceProvider { - return &MetricsTraceProvider{rootPath: rootPath, rootLabels: rootLabels, metrics: metrics} +// NewMetricsTracer creates a new MetricsTracer with the given configuration. +func NewMetricsTracer(metrics *metrics.Metrics, rootPath []string, rootLabels []metrics.Label, logger log.Logger) *MetricsTracer { + return &MetricsTracer{ + Logger: logger, + rootPath: rootPath, + rootLabels: rootLabels, + metrics: metrics, + } } -func (m *MetricsTraceProvider) startSpan(existingPath []string, operation string, _ ...any) log.Span { +func (m *MetricsTracer) startSpan(existingPath []string, operation string, _ ...any) log.Span { path := make([]string, len(existingPath)+1) copy(path, existingPath) path[len(path)-1] = operation return &MetricsSpan{ - MetricsTraceProvider: m, - path: path, - start: time.Now(), + MetricsTracer: m, + path: path, + start: time.Now(), } } -func (m *MetricsTraceProvider) StartSpan(operation string, _ ...any) log.Span { +func (m *MetricsTracer) StartSpan(operation string, _ ...any) log.Span { return m.startSpan(m.rootPath, operation) } -func (m *MetricsTraceProvider) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { +func (m *MetricsTracer) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { return ctx, m.startSpan(m.rootPath, operation) } -func (m *MetricsTraceProvider) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { +func (m *MetricsTracer) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { return ctx, m.startSpan(m.rootPath, operation) } -var _ log.TraceProvider = (*MetricsTraceProvider)(nil) +var _ log.Tracer = (*MetricsTracer)(nil) // MetricsSpan is a log.Span implementation that emits timing and count metrics // to go-metrics when the span ends. @@ -59,19 +69,11 @@ var _ log.TraceProvider = (*MetricsTraceProvider)(nil) // Root path and labels are preserved across all spans created from this tracer, // ensuring consistent metric namespacing and labeling throughout the span hierarchy. type MetricsSpan struct { - *MetricsTraceProvider + *MetricsTracer start time.Time path []string } -// Logger methods are no-ops - MetricsSpan does not support logging. -func (m *MetricsSpan) Info(msg string, keyVals ...any) {} -func (m *MetricsSpan) Warn(msg string, keyVals ...any) {} -func (m *MetricsSpan) Error(msg string, keyVals ...any) {} -func (m *MetricsSpan) Debug(msg string, keyVals ...any) {} -func (m *MetricsSpan) With(keyVals ...any) log.Logger { return m } -func (m *MetricsSpan) Impl() any { return nil } - // StartSpan creates a child span by appending the operation name to the current path. // Root path and labels are preserved in the child span. // The kvs parameters are ignored (metrics don't support dynamic attributes). diff --git a/telemetry/options.go b/telemetry/options.go new file mode 100644 index 000000000000..b48fe041ecd3 --- /dev/null +++ b/telemetry/options.go @@ -0,0 +1,14 @@ +package telemetry + +import "cosmossdk.io/log" + +// Option is a functional option for configuring Metrics. +type Option func(*Metrics) + +// WithLogger sets the default logger to use for tracers. +// If not provided, a nop logger will be used. +func WithLogger(logger log.Logger) Option { + return func(m *Metrics) { + m.logger = logger + } +} diff --git a/telemetry/otel_span.go b/telemetry/otel_span.go index e7c6aea9231f..cfdd9ac6d6de 100644 --- a/telemetry/otel_span.go +++ b/telemetry/otel_span.go @@ -11,17 +11,22 @@ import ( "cosmossdk.io/log" ) -type OtelTraceProvider struct { +// OtelTracer is a log.Tracer implementation that uses OpenTelemetry for distributed tracing. +// It wraps a logger and forwards all log calls to it, while creating OtelSpan instances +// that emit trace spans. +type OtelTracer struct { + log.Logger tracer oteltrace.Tracer } -func NewOtelTraceProvider(tracer oteltrace.Tracer) *OtelTraceProvider { - return &OtelTraceProvider{ +func NewOtelTracer(tracer oteltrace.Tracer, logger log.Logger) *OtelTracer { + return &OtelTracer{ + Logger: logger, tracer: tracer, } } -func (o *OtelTraceProvider) StartSpan(operation string, kvs ...any) log.Span { +func (o *OtelTracer) StartSpan(operation string, kvs ...any) log.Span { ctx, span := o.tracer.Start(context.Background(), operation, oteltrace.WithAttributes(toKVs(kvs)...)) return &OtelSpan{ tracer: o.tracer, @@ -30,7 +35,7 @@ func (o *OtelTraceProvider) StartSpan(operation string, kvs ...any) log.Span { } } -func (o *OtelTraceProvider) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { +func (o *OtelTracer) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...)) return ctx, &OtelSpan{ tracer: o.tracer, @@ -39,7 +44,7 @@ func (o *OtelTraceProvider) StartSpanContext(ctx context.Context, operation stri } } -func (o *OtelTraceProvider) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { +func (o *OtelTracer) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...), oteltrace.WithNewRoot()) return ctx, &OtelSpan{ tracer: o.tracer, @@ -48,7 +53,7 @@ func (o *OtelTraceProvider) StartRootSpan(ctx context.Context, operation string, } } -var _ log.TraceProvider = (*OtelTraceProvider)(nil) +var _ log.Tracer = (*OtelTracer)(nil) type OtelSpan struct { tracer oteltrace.Tracer From 25e3135feb5e3b1b6d358ca8ba4b1e2632c5db46 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 14:32:28 -0400 Subject: [PATCH 43/87] add test setup --- baseapp/baseapp.go | 10 +++++----- simapp/app_di.go | 39 -------------------------------------- simapp/sim_test.go | 8 +++++++- telemetry/otel_span.go | 12 ++++++------ telemetry/testing.go | 43 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 51 deletions(-) create mode 100644 telemetry/testing.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 9de5a9dcaff4..e1de0596ae48 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -174,8 +174,13 @@ type BaseApp struct { func NewBaseApp( name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp), ) *BaseApp { + tracer, ok := logger.(log.Tracer) + if !ok { + tracer = log.NewNopTracer(logger) + } app := &BaseApp{ logger: logger.With(log.ModuleKey, "baseapp"), + tracer: tracer, name: name, db: db, cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), // by default, we use a no-op metric gather in store @@ -189,11 +194,6 @@ func NewBaseApp( } // initialize tracer - tracer, ok := app.logger.(log.Tracer) - if !ok { - tracer = log.NewNopTracer(app.logger) - } - app.tracer = tracer for _, option := range options { option(app) diff --git a/simapp/app_di.go b/simapp/app_di.go index d9516de47564..5e6e9cf26a28 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -3,13 +3,7 @@ package simapp import ( - "context" - "encoding/json" - "fmt" "io" - "os" - "os/signal" - "syscall" dbm "github.com/cosmos/cosmos-db" @@ -18,8 +12,6 @@ import ( storetypes "cosmossdk.io/store/types" "cosmossdk.io/log" - "github.com/cosmos/cosmos-sdk/telemetry" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -108,37 +100,6 @@ func NewSimApp( appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { - // configure metrics and tracing for testing - telemetryCfgJson, ok := os.LookupEnv("SIMAPP_TELEMETRY") - if ok && telemetryCfgJson != "" { - var telemetryCfg telemetry.Config - err := json.Unmarshal([]byte(telemetryCfgJson), &telemetryCfg) - if err != nil { - panic(fmt.Errorf("failed to parse simapp telemetry config: %v\n", err)) - } else { - fmt.Printf("\nConfiguring telemetry for simapp: %+v\n", telemetryCfg) - metrics, err := telemetry.New(telemetryCfg, telemetry.WithLogger(logger)) - if err != nil { - panic(fmt.Errorf("failed to initialize simapp telemetry: %v\n", err)) - } - logger = metrics.Tracer() - err = metrics.Start(context.Background()) - if err != nil { - panic(fmt.Errorf("failed to start simapp telemetry: %v\n", err)) - } - shutdownChan := make(chan os.Signal, 1) - signal.Notify(shutdownChan, os.Interrupt, syscall.SIGTERM) - go func() { - // shutdown when there's a shutdown signal - <-shutdownChan - err := metrics.Shutdown(context.Background()) - if err != nil { - fmt.Printf("failed to shutdown simapp telemetry: %v\n", err) - } - }() - } - } - var ( app = &SimApp{} appBuilder *runtime.AppBuilder diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 55ead7b3ce7f..c1dd2e43bafc 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -19,6 +19,8 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/telemetry" + "cosmossdk.io/store" storetypes "cosmossdk.io/store/types" @@ -189,7 +191,11 @@ func TestAppStateDeterminism(t *testing.T) { return others.Get(k) }) } - return NewSimApp(logger, db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...) + metrics := telemetry.TestingInit(t, nil, logger) + span := metrics.Tracer().StartSpan("test-span") + span.Info("test span created") + span.End() + return NewSimApp(metrics.Tracer(), db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...) } var mx sync.Mutex appHashResults := make(map[int64][][]byte) diff --git a/telemetry/otel_span.go b/telemetry/otel_span.go index cfdd9ac6d6de..3a7db06a24dc 100644 --- a/telemetry/otel_span.go +++ b/telemetry/otel_span.go @@ -65,7 +65,7 @@ type OtelSpan struct { func (o *OtelSpan) addEvent(level, msg string, keyVals ...any) { o.span.AddEvent(msg, oteltrace.WithAttributes(o.persistentAttrs...), - oteltrace.WithAttributes(toKVs(keyVals...)...), + oteltrace.WithAttributes(toKVs(keyVals)...), oteltrace.WithAttributes(otelattr.String("level", level)), ) } @@ -87,7 +87,7 @@ func (o *OtelSpan) Debug(msg string, keyVals ...any) { } func (o *OtelSpan) With(keyVals ...any) log.Logger { - attrs := toKVs(keyVals...) + attrs := toKVs(keyVals) persistentAttrs := make([]otelattr.KeyValue, 0, len(o.persistentAttrs)+len(attrs)) persistentAttrs = append(persistentAttrs, o.persistentAttrs...) persistentAttrs = append(persistentAttrs, attrs...) @@ -107,7 +107,7 @@ func (o *OtelSpan) startSpan(ctx context.Context, operation string, kvs []any, o if len(o.persistentAttrs) > 0 { opts = append(opts, oteltrace.WithAttributes(o.persistentAttrs...)) } - opts = append(opts, oteltrace.WithAttributes(toKVs(kvs...)...)) + opts = append(opts, oteltrace.WithAttributes(toKVs(kvs)...)) ctx, span := o.tracer.Start(ctx, operation, opts...) return &OtelSpan{ tracer: o.tracer, @@ -136,7 +136,7 @@ func (o *OtelSpan) StartRootSpan(ctx context.Context, operation string, kvs ...a } func (o *OtelSpan) SetAttrs(kvs ...any) { - o.span.SetAttributes(toKVs(kvs...)...) + o.span.SetAttributes(toKVs(kvs)...) } func (o *OtelSpan) SetErr(err error, kvs ...any) error { @@ -147,7 +147,7 @@ func (o *OtelSpan) SetErr(err error, kvs ...any) error { o.span.SetStatus(otelcodes.Error, err.Error()) } if len(kvs) > 0 { - o.span.SetAttributes(toKVs(kvs...)...) + o.span.SetAttributes(toKVs(kvs)...) } return err } @@ -158,7 +158,7 @@ func (o *OtelSpan) End() { var _ log.Span = (*OtelSpan)(nil) -func toKVs(kvs ...any) []otelattr.KeyValue { +func toKVs(kvs []any) []otelattr.KeyValue { if len(kvs)%2 != 0 { panic(fmt.Sprintf("kvs must have even length, got %d", len(kvs))) } diff --git a/telemetry/testing.go b/telemetry/testing.go new file mode 100644 index 000000000000..551068088ccd --- /dev/null +++ b/telemetry/testing.go @@ -0,0 +1,43 @@ +package telemetry + +import ( + "context" + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" +) + +// TestingInit initializes telemetry for testing. +// If ctx is nil, context.Background() is used. +// If logger is nil, a new test logger is created. +func TestingInit(t *testing.T, ctx context.Context, logger log.Logger) *Metrics { + t.Helper() + if ctx == nil { + ctx = context.Background() + } + if logger == nil { + logger = log.NewTestLogger(t) + } + + // configure metrics and tracing for testing + telemetryCfg := Config{Enabled: true} + telemetryCfgJson, ok := os.LookupEnv("COSMOS_TELEMETRY") + if ok && telemetryCfgJson != "" { + err := json.Unmarshal([]byte(telemetryCfgJson), &telemetryCfg) + require.NoError(t, err, "failed to parse telemetry config", telemetryCfgJson) + } + + t.Logf("Configuring telemetry with: %+v", telemetryCfg) + metrics, err := New(telemetryCfg, WithLogger(logger)) + require.NoError(t, err, "failed to initialize telemetry") + err = metrics.Start(ctx) + require.NoError(t, err, "failed to start telemetry") + t.Cleanup(func() { + require.NoError(t, metrics.Shutdown(ctx), "failed to shutdown telemetry") + }) + return metrics +} From 5c7e464a797680cd6e5f113f9ab6f5287493fba3 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 16:56:47 -0400 Subject: [PATCH 44/87] fix shutdown order --- telemetry/metrics.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/telemetry/metrics.go b/telemetry/metrics.go index 3bcb777918bb..db56cd76535a 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -323,8 +323,10 @@ func (m *Metrics) Start(ctx context.Context) error { // Shutdown must be called before the application exits to shutdown any // exporters and close any open files. func (m *Metrics) Shutdown(ctx context.Context) error { - for _, f := range m.shutdownFuncs { - if err := f(ctx); err != nil { + n := len(m.shutdownFuncs) + // shutdown in reverse order because some exporters may depend on others + for i := n - 1; i >= 0; i-- { + if err := m.shutdownFuncs[i](ctx); err != nil { return err } } From d71f7c15086307a903b2d4bddd12764f9923a080 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 17:18:52 -0400 Subject: [PATCH 45/87] block trace nesting --- baseapp/abci.go | 24 ++++++++++++++---------- baseapp/baseapp.go | 2 ++ simapp/sim_test.go | 7 +++---- telemetry/metrics.go | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index dd4999af0334..5254a2c7d60a 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -39,7 +39,7 @@ const ( ) func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { - span := app.tracer.StartSpan("InitChain") + span := app.blockSpan.StartSpan("InitChain") defer span.End() if req.ChainId != app.chainID { @@ -401,7 +401,7 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) { - span := app.tracer.StartSpan("PrepareProposal") + span := app.blockSpan.StartSpan("PrepareProposal") defer span.End() if app.abciHandlers.PrepareProposalHandler == nil { @@ -491,7 +491,7 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { - span := app.tracer.StartSpan("ProcessProposal") + span := app.blockSpan.StartSpan("ProcessProposal") defer span.End() if app.abciHandlers.ProcessProposalHandler == nil { @@ -591,7 +591,7 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc // height and are committed in the subsequent height, i.e. H+2. An error is // returned if vote extensions are not enabled or if extendVote fails or panics. func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (resp *abci.ResponseExtendVote, err error) { - span := app.tracer.StartSpan("ExtendVote") + span := app.blockSpan.StartSpan("ExtendVote") defer span.End() // Always reset state given that ExtendVote and VerifyVoteExtension can timeout @@ -667,7 +667,7 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) ( // phase. The response MUST be deterministic. An error is returned if vote // extensions are not enabled or if verifyVoteExt fails or panics. func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (resp *abci.ResponseVerifyVoteExtension, err error) { - span := app.tracer.StartSpan("VerifyVoteExtension") + span := app.blockSpan.StartSpan("VerifyVoteExtension") defer span.End() if app.abciHandlers.VerifyVoteExtensionHandler == nil { @@ -738,7 +738,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r // only used to handle early cancellation, for anything related to state app.stateManager.GetState(execModeFinalize).Context() // must be used. func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { - span := app.tracer.StartSpan("internalFinalizeBlock") + ctx, span := app.tracer.StartSpanContext(ctx, "internalFinalizeBlock") defer span.End() var events []abci.Event @@ -916,7 +916,7 @@ func (app *BaseApp) executeTxsWithExecutor(ctx context.Context, ms storetypes.Mu // extensions into the proposal, which should not themselves be executed in cases // where they adhere to the sdk.Tx interface. func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.ResponseFinalizeBlock, err error) { - span := app.tracer.StartSpan("FinalizeBlock") + ctx, span := app.blockSpan.StartSpanContext(context.Background(), "FinalizeBlock") defer span.End() defer func() { @@ -952,7 +952,7 @@ func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.Res } // if no OE is running, just run the block (this is either a block replay or a OE that got aborted) - res, err = app.internalFinalizeBlock(context.Background(), req) + res, err = app.internalFinalizeBlock(ctx, req) if res != nil { res.AppHash = app.workingHash() } @@ -986,8 +986,12 @@ func (app *BaseApp) checkHalt(height int64, time time.Time) error { // against that height and gracefully halt if it matches the latest committed // height. func (app *BaseApp) Commit() (*abci.ResponseCommit, error) { - span := app.tracer.StartSpan("Commit") - defer span.End() + span := app.blockSpan.StartSpan("Commit") + defer func() { + span.End() + app.blockSpan.End() + app.blockSpan = app.tracer.StartSpan("block") + }() finalizeState := app.stateManager.GetState(execModeFinalize) header := finalizeState.Context().BlockHeader() diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index e1de0596ae48..4cc51ae8c3da 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -66,6 +66,7 @@ type BaseApp struct { mu sync.Mutex // mu protects the fields below. logger log.Logger tracer log.Tracer + blockSpan log.Span name string // application name from abci.BlockInfo db dbm.DB // common DB backend cms storetypes.CommitMultiStore // Main (uncached) state @@ -181,6 +182,7 @@ func NewBaseApp( app := &BaseApp{ logger: logger.With(log.ModuleKey, "baseapp"), tracer: tracer, + blockSpan: tracer.StartSpan("block"), name: name, db: db, cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), // by default, we use a no-op metric gather in store diff --git a/simapp/sim_test.go b/simapp/sim_test.go index c1dd2e43bafc..848911ef2ffd 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -192,10 +192,9 @@ func TestAppStateDeterminism(t *testing.T) { }) } metrics := telemetry.TestingInit(t, nil, logger) - span := metrics.Tracer().StartSpan("test-span") - span.Info("test span created") - span.End() - return NewSimApp(metrics.Tracer(), db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...) + baseAppSpan := metrics.Tracer().StartSpan("baseapp") + t.Cleanup(baseAppSpan.End) + return NewSimApp(baseAppSpan, db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...) } var mx sync.Mutex appHashResults := make(map[int64][][]byte) diff --git a/telemetry/metrics.go b/telemetry/metrics.go index db56cd76535a..0d47e63ccb55 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -324,7 +324,7 @@ func (m *Metrics) Start(ctx context.Context) error { // exporters and close any open files. func (m *Metrics) Shutdown(ctx context.Context) error { n := len(m.shutdownFuncs) - // shutdown in reverse order because some exporters may depend on others + // shutdown in reverse order because we can't close files until after the exporter is stopped for i := n - 1; i >= 0; i-- { if err := m.shutdownFuncs[i](ctx); err != nil { return err From 56b215a47725894396df66385b74b32dc12305e1 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 19:50:44 -0400 Subject: [PATCH 46/87] update metrics config and instrumentation --- .gitignore | 2 ++ baseapp/test_helpers.go | 4 +++- telemetry/config.go | 5 +++++ telemetry/metrics.go | 32 +++++++++++++++++++++++++++----- telemetry/testing.go | 5 ++++- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 4ce3ebf35d58..621132f18ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ debug_container.log *.synctex.gz /x/genutil/config/priv_validator_key.json /x/genutil/data/priv_validator_state.json +/.envrc +/.env diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index 12695792d659..f303bb4d8efa 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -31,13 +31,15 @@ func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { } func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { + span := app.blockSpan.StartSpan("SimDeliver") + defer span.End() // See comment for Check(). bz, err := txEncoder(tx) if err != nil { return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, span) return gasInfo, result, err } diff --git a/telemetry/config.go b/telemetry/config.go index 1e9eeb52ac97..6e68a832a27d 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -141,6 +141,11 @@ type OtelTraceExportConfig struct { // Insecure disables TLS certificate verification for OTLP exporters. Insecure bool `mapstructure:"insecure" json:"insecure"` + // Headers is a map of HTTP headers to send with each request to the OTLP exporter. + // Useful for authentication, authorization, or custom metadata. + // Example: {"Authorization": "Bearer token123", "X-Custom-Header": "value"} + Headers map[string]string `mapstructure:"headers" json:"headers"` + // File is the file path to write trace data to when using the "stdout" exporter. // If it is empty, the trace data is written to stdout. File string `mapstructure:"file" json:"file"` diff --git a/telemetry/metrics.go b/telemetry/metrics.go index 0d47e63ccb55..b668d32f4982 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -18,7 +18,9 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" otelsdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "cosmossdk.io/log" ) @@ -158,12 +160,18 @@ func New(cfg Config, opts ...Option) (_ *Metrics, rerr error) { if exporterOpts.Insecure { opts = append(opts, otlptracegrpc.WithInsecure()) } + if len(exporterOpts.Headers) > 0 { + opts = append(opts, otlptracegrpc.WithHeaders(exporterOpts.Headers)) + } client = otlptracegrpc.NewClient(opts...) default: opts := []otlptracehttp.Option{otlptracehttp.WithEndpoint(endpoint)} if exporterOpts.Insecure { opts = append(opts, otlptracehttp.WithInsecure()) } + if len(exporterOpts.Headers) > 0 { + opts = append(opts, otlptracehttp.WithHeaders(exporterOpts.Headers)) + } client = otlptracehttp.NewClient(opts...) } exporter := otlptrace.NewUnstarted(client) @@ -196,13 +204,27 @@ func New(cfg Config, opts ...Option) (_ *Metrics, rerr error) { } } + // Determine service name for both resource and tracer + serviceName := cfg.ServiceName + if serviceName == "" { + serviceName = "cosmos-sdk" + } + + // Create OpenTelemetry resource with service name + res, err := resource.New(context.Background(), + resource.WithAttributes( + semconv.ServiceName(serviceName), + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create trace resource: %w", err) + } + + // Add resource to tracer provider options + tracerProviderOpts = append(tracerProviderOpts, otelsdktrace.WithResource(res)) tracerProvider := otelsdktrace.NewTracerProvider(tracerProviderOpts...) m.shutdownFuncs = append(m.shutdownFuncs, tracerProvider.Shutdown) - tracerName := cfg.ServiceName - if tracerName == "" { - tracerName = "cosmos-sdk" - } - m.tracer = NewOtelTracer(tracerProvider.Tracer(tracerName), m.logger) + m.tracer = NewOtelTracer(tracerProvider.Tracer(serviceName), m.logger) case "metrics": m.tracer = m.metricsTracer default: diff --git a/telemetry/testing.go b/telemetry/testing.go index 551068088ccd..f259e978e293 100644 --- a/telemetry/testing.go +++ b/telemetry/testing.go @@ -24,7 +24,10 @@ func TestingInit(t *testing.T, ctx context.Context, logger log.Logger) *Metrics } // configure metrics and tracing for testing - telemetryCfg := Config{Enabled: true} + telemetryCfg := Config{ + Enabled: true, + ServiceName: "cosmos-sdk-test", + } telemetryCfgJson, ok := os.LookupEnv("COSMOS_TELEMETRY") if ok && telemetryCfgJson != "" { err := json.Unmarshal([]byte(telemetryCfgJson), &telemetryCfg) From 98b1d3816c9abff60570328431bf100ed14d381c Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 20:01:20 -0400 Subject: [PATCH 47/87] tidy --- client/v2/go.mod | 1 + client/v2/go.sum | 1 + systemtests/go.mod | 1 + systemtests/go.sum | 1 + 4 files changed, 4 insertions(+) diff --git a/client/v2/go.mod b/client/v2/go.mod index 7d46f2c4ca34..2347a2c1ebc8 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -64,6 +64,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index 58676ed6c45a..1f921699a087 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -201,6 +201,7 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/systemtests/go.mod b/systemtests/go.mod index 8fd0c16fb18a..044ede32da27 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -61,6 +61,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index ae9b36f2df21..5f2263b90064 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -201,6 +201,7 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= From 80b7dc1b8d0c59383b45214329d3015021458dc3 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 30 Oct 2025 20:06:06 -0400 Subject: [PATCH 48/87] fixes --- iavlx/commit_multi_tree.go | 9 ++++----- iavlx/commit_tree.go | 2 +- iavlx/immutable_tree.go | 6 ++---- iavlx/multi_tree.go | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/iavlx/commit_multi_tree.go b/iavlx/commit_multi_tree.go index 606ac26b8da9..a41fbc259de0 100644 --- a/iavlx/commit_multi_tree.go +++ b/iavlx/commit_multi_tree.go @@ -65,7 +65,7 @@ func (db *CommitMultiTree) WorkingHash() []byte { var wg sync.WaitGroup for i, tree := range db.trees { wg.Add(1) - go func(i int, t storetypes.CommitKVStore) { + go func(i int, t storetypes.CommitStore) { defer wg.Done() hashes[i] = t.WorkingHash() }(i, tree) @@ -98,7 +98,7 @@ func (db *CommitMultiTree) Commit() storetypes.CommitID { var wg sync.WaitGroup for i, tree := range db.trees { wg.Add(1) - go func(i int, t storetypes.CommitKVStore) { + go func(i int, t storetypes.CommitStore) { defer wg.Done() hashes[i] = t.Commit().Hash }(i, tree) @@ -194,12 +194,11 @@ func (db *CommitMultiTree) CacheMultiStoreWithVersion(version int64) (storetypes typ := db.storeTypes[i] switch typ { case storetypes.StoreTypeIAVL, storetypes.StoreTypeDB: - var err error - var t storetypes.KVStore - t, err = tree.(*CommitTree).GetImmutable(version) + t, err := tree.(*CommitTree).GetImmutable(version) if err != nil { return nil, fmt.Errorf("failed to create cache multi store for tree %s at version %d: %w", db.treeKeys[i].Name(), version, err) } + mt.trees[i] = t.CacheWrap() default: mt.trees[i] = tree.CacheWrap() } diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go index b0a1f5fc015a..749a418745cf 100644 --- a/iavlx/commit_tree.go +++ b/iavlx/commit_tree.go @@ -324,7 +324,7 @@ func (c *CommitTree) startEvict(evictVersion uint32) { }() } -func (c *CommitTree) GetImmutable(version int64) (storetypes.CacheWrap, error) { +func (c *CommitTree) GetImmutable(version int64) (storetypes.KVStore, error) { var rootPtr *NodePointer if version == c.lastCommitId.Version { rootPtr = c.root diff --git a/iavlx/immutable_tree.go b/iavlx/immutable_tree.go index 0e5aa4ee387d..77c01c4cb3c8 100644 --- a/iavlx/immutable_tree.go +++ b/iavlx/immutable_tree.go @@ -3,8 +3,6 @@ package iavlx import ( io "io" - corestore "cosmossdk.io/core/store" - storetypes "cosmossdk.io/store/types" ) @@ -62,11 +60,11 @@ func (tree *ImmutableTree) Has(key []byte) bool { return val != nil } -func (tree *ImmutableTree) Iterator(start, end []byte) corestore.Iterator { +func (tree *ImmutableTree) Iterator(start, end []byte) storetypes.Iterator { return NewIterator(start, end, true, tree.root, true) } -func (tree *ImmutableTree) ReverseIterator(start, end []byte) corestore.Iterator { +func (tree *ImmutableTree) ReverseIterator(start, end []byte) storetypes.Iterator { return NewIterator(start, end, false, tree.root, true) } diff --git a/iavlx/multi_tree.go b/iavlx/multi_tree.go index 54bf08ce1e80..46c3465beabb 100644 --- a/iavlx/multi_tree.go +++ b/iavlx/multi_tree.go @@ -52,7 +52,7 @@ func (t *MultiTree) Write() { for _, tree := range t.trees { // TODO check if trees are dirty before spinning off a goroutine wg.Add(1) - go func(t storetypes.CacheKVStore) { + go func(t storetypes.CacheWrap) { defer wg.Done() t.Write() }(tree) From f9ce55cb6fb9e6c68610434a096dcab0a649addc Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 10:07:51 -0400 Subject: [PATCH 49/87] start adding otel metric config --- go.mod | 16 ++++++++++++++-- go.sum | 28 ++++++++++++++++++++++++++++ telemetry/config.go | 37 +++++++++++++++++++++++++++---------- telemetry/metrics.go | 10 ++++++++++ 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d696ee4f4640..c53a80266aad 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/tidwall/btree v1.8.1 go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 go.opentelemetry.io/otel/sdk v1.38.0 @@ -162,6 +162,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect @@ -194,7 +195,8 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect @@ -217,7 +219,17 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 693c2dcbb741..0df3549e96fa 100644 --- a/go.sum +++ b/go.sum @@ -439,6 +439,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -706,6 +708,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -715,6 +719,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -839,22 +845,44 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= diff --git a/telemetry/config.go b/telemetry/config.go index 6e68a832a27d..42b78413e73e 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -5,6 +5,7 @@ import ( "github.com/hashicorp/go-metrics" "github.com/prometheus/common/expfmt" + otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0" ) // globalTelemetryEnabled is a private variable that stores the telemetry enabled state. @@ -115,41 +116,57 @@ type Config struct { // - "noop": No-op trace sink (default) TraceSink string `mapstructure:"trace-sink" json:"trace-sink"` - // OtelTraceExporters is a list of OTLP exporters to use for trace data. + // OtelTraceExporters is a list of exporters to use for trace data. // This is only used when trace sink is set to "otel". - OtelTraceExporters []OtelTraceExportConfig `mapstructure:"otel-trace-exporters" json:"otel-trace-exporters"` + OtelTraceExporters []OtelExportConfig `mapstructure:"otel-trace-exporters" json:"otel-trace-exporters"` + + // OtelMetricsExporters is a list of exporters to use for metrics data via OpenTelemetry. + // When configured, wrapper functions (IncrCounter, SetGauge, etc.) will use OTel instruments + // instead of go-metrics, and metrics will be exported through these exporters. + OtelMetricsExporters []OtelExportConfig `mapstructure:"otel-metrics-exporters" json:"otel-metrics-exporters"` } -type OtelTraceExportConfig struct { +// OtelExportConfig defines configuration for an OpenTelemetry exporter. +// This is used for both traces and metrics exporters. +type OtelExportConfig struct { // Type is the exporter type. - // Must be one of: - // - "stdout" - // - "otlp" - // - // OTLP exporters must set the endpoint URL and can optionally set the transport protocol. + // For traces: "stdout", "otlp" + // For metrics: "stdout", "otlp", "prometheus" Type string `mapstructure:"type" json:"type"` // OTLPTransport is the transport protocol to use for OTLP. // Must be one of: // - "http" (default) // - "grpc" + // Only used when Type is "otlp". OTLPTransport string `mapstructure:"otlp-transport" json:"otlp-transport"` // Endpoint is the OTLP exporter endpoint URL (grpc or http). + // Only used when Type is "otlp". + // Example: "localhost:4318" for HTTP, "localhost:4317" for gRPC Endpoint string `mapstructure:"endpoint" json:"endpoint"` // Insecure disables TLS certificate verification for OTLP exporters. + // Only used when Type is "otlp". Insecure bool `mapstructure:"insecure" json:"insecure"` // Headers is a map of HTTP headers to send with each request to the OTLP exporter. // Useful for authentication, authorization, or custom metadata. + // Only used when Type is "otlp". // Example: {"Authorization": "Bearer token123", "X-Custom-Header": "value"} Headers map[string]string `mapstructure:"headers" json:"headers"` - // File is the file path to write trace data to when using the "stdout" exporter. - // If it is empty, the trace data is written to stdout. + // File is the file path to write data to when using the "stdout" exporter. + // If empty, data is written to stdout. + // Only used when Type is "stdout". File string `mapstructure:"file" json:"file"` // PrettyPrint enables pretty-printing of JSON output when using the "stdout" exporter. + // Only used when Type is "stdout" for traces. PrettyPrint bool `mapstructure:"pretty-print" json:"pretty-print"` + + // ListenAddress is the address to listen on for Prometheus scraping. + // Only used when Type is "prometheus" for metrics. + // Example: ":8080" or "localhost:9090" + ListenAddress string `mapstructure:"listen-address" json:"listen-address"` } diff --git a/telemetry/metrics.go b/telemetry/metrics.go index b668d32f4982..eb4491099e65 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -46,6 +46,16 @@ type Metrics struct { logger log.Logger tracer log.Tracer metricsTracer *MetricsTracer + + // OpenTelemetry metrics (when OtelMetricsExporters is configured) + meterProvider *sdkmetric.MeterProvider + meter metric.Meter + + // Instrument cache for wrapper functions + counters map[string]metric.Int64Counter + gauges map[string]metric.Float64Gauge + histograms map[string]metric.Float64Histogram + mu sync.RWMutex } // GatherResponse contains collected metrics in the requested format. From 3fff00f53ee6ef9949247ffad9c090a8ef4c8421 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 13:32:49 -0400 Subject: [PATCH 50/87] migrate to pure otel setup --- baseapp/abci.go | 75 ++++--- baseapp/baseapp.go | 72 +++---- baseapp/genesis.go | 2 +- baseapp/test_helpers.go | 11 +- baseapp/txnrunner/default.go | 7 +- blockstm/executor.go | 4 +- blockstm/txnrunner.go | 5 +- blockstm/types.go | 4 +- client/v2/go.mod | 18 +- client/v2/go.sum | 35 +++- go.mod | 17 +- go.sum | 9 +- log/tracer.go | 99 ---------- server/api/server.go | 28 --- server/config/config.go | 19 +- server/start.go | 28 +-- simapp/go.mod | 17 +- simapp/go.sum | 37 +++- simapp/sim_test.go | 11 +- systemtests/go.mod | 18 +- systemtests/go.sum | 35 +++- telemetry/config.go | 192 ++++-------------- telemetry/doc.go | 39 +--- telemetry/file_sink.go | 157 --------------- telemetry/file_sink_test.go | 182 ----------------- telemetry/metrics.go | 366 ----------------------------------- telemetry/metrics_span.go | 139 ------------- telemetry/metrics_test.go | 108 ----------- telemetry/options.go | 14 -- telemetry/otel_span.go | 203 ------------------- telemetry/testing.go | 37 +--- telemetry/wrapper.go | 2 + tests/go.mod | 17 +- tests/go.sum | 37 +++- tests/systemtests/go.mod | 18 +- tests/systemtests/go.sum | 35 +++- testutil/network/network.go | 4 +- types/abci.go | 4 +- types/context.go | 9 +- 39 files changed, 391 insertions(+), 1723 deletions(-) delete mode 100644 log/tracer.go delete mode 100644 telemetry/file_sink.go delete mode 100644 telemetry/file_sink_test.go delete mode 100644 telemetry/metrics.go delete mode 100644 telemetry/metrics_span.go delete mode 100644 telemetry/metrics_test.go delete mode 100644 telemetry/options.go delete mode 100644 telemetry/otel_span.go diff --git a/baseapp/abci.go b/baseapp/abci.go index 5254a2c7d60a..e1aabdc64216 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -39,7 +39,7 @@ const ( ) func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { - span := app.blockSpan.StartSpan("InitChain") + _, span := tracer.Start(context.Background(), "InitChain") defer span.End() if req.ChainId != app.chainID { @@ -155,8 +155,8 @@ func (app *BaseApp) Info(_ *abci.RequestInfo) (*abci.ResponseInfo, error) { // Query implements the ABCI interface. It delegates to CommitMultiStore if it // implements Queryable. -func (app *BaseApp) Query(_ context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) { - span := app.tracer.StartSpan("Query") +func (app *BaseApp) Query(ctx context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) { + ctx, span := tracer.Start(ctx, "Query") defer span.End() // add panic recovery for all queries @@ -348,7 +348,7 @@ func (app *BaseApp) ApplySnapshotChunk(req *abci.RequestApplySnapshotChunk) (*ab // will contain relevant error information. Regardless of tx execution outcome, // the ResponseCheckTx will contain the relevant gas execution context. func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { - span := app.tracer.StartSpan("CheckTx") + _, span := tracer.Start(context.Background(), "CheckTx") defer span.End() var mode sdk.ExecMode @@ -365,7 +365,7 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er } if app.abciHandlers.CheckTxHandler == nil { - gasInfo, result, anteEvents, err := app.RunTx(mode, req.Tx, nil, -1, nil, nil, span) + gasInfo, result, anteEvents, err := app.RunTx(mode, req.Tx, nil, -1, nil, nil) if err != nil { return sdkerrors.ResponseCheckTxWithEvents(err, gasInfo.GasWanted, gasInfo.GasUsed, anteEvents, app.trace), nil } @@ -381,7 +381,7 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er // Create wrapper to avoid users overriding the execution mode runTx := func(txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { - return app.RunTx(mode, txBytes, tx, -1, nil, nil, span) + return app.RunTx(mode, txBytes, tx, -1, nil, nil) } return app.abciHandlers.CheckTxHandler(runTx, req) @@ -401,9 +401,6 @@ func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) { - span := app.blockSpan.StartSpan("PrepareProposal") - defer span.End() - if app.abciHandlers.PrepareProposalHandler == nil { return nil, errors.New("PrepareProposal handler not set") } @@ -466,7 +463,10 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc } }() - resp, err = app.abciHandlers.PrepareProposalHandler(prepareProposalState.Context(), req) + ctx := prepareProposalState.Context() + ctx, span := ctx.StartSpan(tracer, "PrepareProposal") + defer span.End() + resp, err = app.abciHandlers.PrepareProposalHandler(ctx, req) if err != nil { app.logger.Error("failed to prepare proposal", "height", req.Height, "time", req.Time, "err", err) return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil @@ -491,9 +491,6 @@ func (app *BaseApp) PrepareProposal(req *abci.RequestPrepareProposal) (resp *abc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { - span := app.blockSpan.StartSpan("ProcessProposal") - defer span.End() - if app.abciHandlers.ProcessProposalHandler == nil { return nil, errors.New("ProcessProposal handler not set") } @@ -528,7 +525,10 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc } processProposalState := app.stateManager.GetState(execModeProcessProposal) - processProposalState.SetContext(app.getContextForProposal(processProposalState.Context(), req.Height). + ctx := processProposalState.Context() + ctx, span := ctx.StartSpan(tracer, "ProcessProposal") + defer span.End() + processProposalState.SetContext(app.getContextForProposal(ctx, req.Height). WithVoteInfos(req.ProposedLastCommit.Votes). // this is a set of votes that are not finalized yet, wait for commit WithBlockHeight(req.Height). WithBlockTime(req.Time). @@ -591,9 +591,6 @@ func (app *BaseApp) ProcessProposal(req *abci.RequestProcessProposal) (resp *abc // height and are committed in the subsequent height, i.e. H+2. An error is // returned if vote extensions are not enabled or if extendVote fails or panics. func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (resp *abci.ResponseExtendVote, err error) { - span := app.blockSpan.StartSpan("ExtendVote") - defer span.End() - // Always reset state given that ExtendVote and VerifyVoteExtension can timeout // and be called again in a subsequent round. var ctx sdk.Context @@ -613,6 +610,9 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) ( return nil, errors.New("application ExtendVote handler not set") } + ctx, span := ctx.StartSpan(tracer, "ExtendVote") + defer span.End() + // If vote extensions are not enabled, as a safety precaution, we return an // error. cp := app.GetConsensusParams(ctx) @@ -667,9 +667,6 @@ func (app *BaseApp) ExtendVote(_ context.Context, req *abci.RequestExtendVote) ( // phase. The response MUST be deterministic. An error is returned if vote // extensions are not enabled or if verifyVoteExt fails or panics. func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (resp *abci.ResponseVerifyVoteExtension, err error) { - span := app.blockSpan.StartSpan("VerifyVoteExtension") - defer span.End() - if app.abciHandlers.VerifyVoteExtensionHandler == nil { return nil, errors.New("application VerifyVoteExtension handler not set") } @@ -687,6 +684,9 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r ctx = sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager) } + ctx, span := ctx.StartSpan(tracer, "VerifyVoteExtension") + defer span.End() + // If vote extensions are not enabled, as a safety precaution, we return an // error. cp := app.GetConsensusParams(ctx) @@ -737,10 +737,7 @@ func (app *BaseApp) VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (r // Execution flow or by the FinalizeBlock ABCI method. The context received is // only used to handle early cancellation, for anything related to state app.stateManager.GetState(execModeFinalize).Context() // must be used. -func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { - ctx, span := app.tracer.StartSpanContext(ctx, "internalFinalizeBlock") - defer span.End() - +func (app *BaseApp) internalFinalizeBlock(goCtx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { var events []abci.Event if err := app.checkHalt(req.Height, req.Time); err != nil { @@ -774,9 +771,12 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request app.stateManager.SetState(execModeFinalize, app.cms, header, app.logger, app.streamingManager) finalizeState = app.stateManager.GetState(execModeFinalize) } + ctx := finalizeState.Context().WithContext(goCtx) + ctx, span := ctx.StartSpan(tracer, "internalFinalizeBlock") + defer span.End() // Context is now updated with Header information. - finalizeState.SetContext(finalizeState.Context(). + finalizeState.SetContext(ctx. WithBlockHeader(header). WithHeaderHash(req.Hash). WithHeaderInfo(coreheader.Info{ @@ -806,14 +806,14 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request WithHeaderHash(req.Hash)) } - preblockEvents, err := app.preBlock(span, req) + preblockEvents, err := app.preBlock(req) if err != nil { return nil, err } events = append(events, preblockEvents...) - beginBlock, err := app.beginBlock(span, req) + beginBlock, err := app.beginBlock(req) if err != nil { return nil, err } @@ -870,7 +870,7 @@ func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.Request WithBlockGasUsed(blockGasUsed). WithBlockGasWanted(blockGasWanted), ) - endBlock, err := app.endBlock(span, finalizeState.Context()) + endBlock, err := app.endBlock() if err != nil { return nil, err } @@ -898,7 +898,6 @@ func (app *BaseApp) executeTxsWithExecutor(ctx context.Context, ms storetypes.Mu if app.txRunner == nil { app.txRunner = txnrunner.NewDefaultRunner( app.txDecoder, - app.tracer, ) } @@ -916,9 +915,6 @@ func (app *BaseApp) executeTxsWithExecutor(ctx context.Context, ms storetypes.Mu // extensions into the proposal, which should not themselves be executed in cases // where they adhere to the sdk.Tx interface. func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.ResponseFinalizeBlock, err error) { - ctx, span := app.blockSpan.StartSpanContext(context.Background(), "FinalizeBlock") - defer span.End() - defer func() { if res == nil { return @@ -952,7 +948,7 @@ func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.Res } // if no OE is running, just run the block (this is either a block replay or a OE that got aborted) - res, err = app.internalFinalizeBlock(ctx, req) + res, err = app.internalFinalizeBlock(context.Background(), req) if res != nil { res.AppHash = app.workingHash() } @@ -986,15 +982,12 @@ func (app *BaseApp) checkHalt(height int64, time time.Time) error { // against that height and gracefully halt if it matches the latest committed // height. func (app *BaseApp) Commit() (*abci.ResponseCommit, error) { - span := app.blockSpan.StartSpan("Commit") - defer func() { - span.End() - app.blockSpan.End() - app.blockSpan = app.tracer.StartSpan("block") - }() - finalizeState := app.stateManager.GetState(execModeFinalize) - header := finalizeState.Context().BlockHeader() + ctx := finalizeState.Context() + ctx, span := ctx.StartSpan(tracer, "Commit") + defer span.End() + + header := ctx.BlockHeader() retainHeight := app.GetBlockRetentionHeight(header.Height) if app.abciHandlers.Precommiter != nil { diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 4cc51ae8c3da..fb502333e6f6 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1,7 +1,6 @@ package baseapp import ( - "context" "fmt" "maps" "math" @@ -15,6 +14,9 @@ import ( cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/gogoproto/proto" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" protov2 "google.golang.org/protobuf/proto" errorsmod "cosmossdk.io/errors" @@ -32,6 +34,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/telemetry" + _ "github.com/cosmos/cosmos-sdk/telemetry" // need to initialize telemetry before we declare tracer and metrics sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/mempool" @@ -60,13 +63,15 @@ const ( var _ servertypes.ABCI = (*BaseApp)(nil) +var ( + tracer trace.Tracer = otel.Tracer("baseapp") +) + // BaseApp reflects the ABCI application implementation. type BaseApp struct { // initialized on creation mu sync.Mutex // mu protects the fields below. logger log.Logger - tracer log.Tracer - blockSpan log.Span name string // application name from abci.BlockInfo db dbm.DB // common DB backend cms storetypes.CommitMultiStore // Main (uncached) state @@ -175,14 +180,8 @@ type BaseApp struct { func NewBaseApp( name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp), ) *BaseApp { - tracer, ok := logger.(log.Tracer) - if !ok { - tracer = log.NewNopTracer(logger) - } app := &BaseApp{ logger: logger.With(log.ModuleKey, "baseapp"), - tracer: tracer, - blockSpan: tracer.StartSpan("block"), name: name, db: db, cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), // by default, we use a no-op metric gather in store @@ -662,14 +661,13 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context return ctx.WithMultiStore(msCache), msCache } -func (app *BaseApp) preBlock(tracer log.Tracer, req *abci.RequestFinalizeBlock) ([]abci.Event, error) { - span := tracer.StartSpan("preBlock") - defer span.End() - +func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) { var events []abci.Event if app.abciHandlers.PreBlocker != nil { finalizeState := app.stateManager.GetState(execModeFinalize) ctx := finalizeState.Context().WithEventManager(sdk.NewEventManager()) + ctx, span := ctx.StartSpan(tracer, "preBlock") + defer span.End() rsp, err := app.abciHandlers.PreBlocker(ctx, req) if err != nil { return nil, err @@ -688,14 +686,17 @@ func (app *BaseApp) preBlock(tracer log.Tracer, req *abci.RequestFinalizeBlock) return events, nil } -func (app *BaseApp) beginBlock(tracer log.Tracer, _ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) { +func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) { var ( resp sdk.BeginBlock err error ) if app.abciHandlers.BeginBlocker != nil { - resp, err = app.abciHandlers.BeginBlocker(app.stateManager.GetState(execModeFinalize).Context()) + ctx := app.stateManager.GetState(execModeFinalize).Context() + ctx, span := ctx.StartSpan(tracer, "beginBlock") + defer span.End() + resp, err = app.abciHandlers.BeginBlocker(ctx) if err != nil { return resp, err } @@ -714,7 +715,7 @@ func (app *BaseApp) beginBlock(tracer log.Tracer, _ *abci.RequestFinalizeBlock) return resp, nil } -func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txIndex int, incarnationCache map[string]any, tracer log.Tracer) *abci.ExecTxResult { +func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txIndex int, incarnationCache map[string]any) *abci.ExecTxResult { gInfo := sdk.GasInfo{} resultStr := "successful" @@ -727,7 +728,7 @@ func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txI telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") }() - gInfo, result, anteEvents, err := app.RunTx(execModeFinalize, tx, nil, txIndex, txMultiStore, incarnationCache, tracer) + gInfo, result, anteEvents, err := app.RunTx(execModeFinalize, tx, nil, txIndex, txMultiStore, incarnationCache) if err != nil { resultStr = "failed" resp = sdkerrors.ResponseExecTxResultWithEvents( @@ -753,14 +754,14 @@ func (app *BaseApp) deliverTx(tx []byte, txMultiStore storetypes.MultiStore, txI // endBlock is an application-defined function that is called after transactions // have been processed in FinalizeBlock. -func (app *BaseApp) endBlock(tracer log.Tracer, _ context.Context) (sdk.EndBlock, error) { - span := tracer.StartSpan("endBlock") - defer span.End() - +func (app *BaseApp) endBlock() (sdk.EndBlock, error) { var endblock sdk.EndBlock if app.abciHandlers.EndBlocker != nil { - eb, err := app.abciHandlers.EndBlocker(app.stateManager.GetState(execModeFinalize).Context()) + ctx := app.stateManager.GetState(execModeFinalize).Context() + ctx, span := ctx.StartSpan(tracer, "endBlock") + defer span.End() + eb, err := app.abciHandlers.EndBlocker(ctx) if err != nil { return endblock, err } @@ -789,8 +790,9 @@ func (app *BaseApp) endBlock(tracer log.Tracer, _ context.Context) (sdk.EndBlock // and execute successfully. An error is returned otherwise. // both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice // passing the decoded tx to runTX is optional, it will be decoded if the tx is nil -func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any, tracer log.Tracer) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { - span := tracer.StartSpan("RunTx", "txBytes", txBytes, "txIndex", txIndex, "mode", mode) +func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { + ctx := app.getContextForTx(mode, txBytes, txIndex) + ctx, span := ctx.StartSpan(tracer, "runTx") defer span.End() // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is @@ -798,7 +800,6 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // meter, so we initialize upfront. var gasWanted uint64 - ctx := app.getContextForTx(mode, txBytes, txIndex) if incarnationCache != nil { ctx = ctx.WithIncarnationCache(incarnationCache) } @@ -881,7 +882,7 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // performance benefits, but it'll be more difficult to get right. anteCtx, msCache = app.cacheTxContext(ctx, txBytes) anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) - anteSpan := tracer.StartSpan("anteHandler") + anteCtx, anteSpan := anteCtx.StartSpan(tracer, "anteHandler") newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate) anteSpan.End() @@ -932,7 +933,6 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // in case message processing fails. At this point, the MultiStore // is a branch of a branch. runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) - runMsgCtx = runMsgCtx.WithLogger(tracer) // attach the tracer to the context as the logger // Attempt to execute all messages and only update state if all messages pass // and we're in DeliverTx. Note, runMsgs will never return a reference to a @@ -991,12 +991,7 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex // Handler does not exist for a given message route. Otherwise, a reference to a // Result is returned. The caller must not commit state if an error is returned. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode sdk.ExecMode) (*sdk.Result, error) { - // extract the tracer from the context - tracer, ok := ctx.Logger().(log.Tracer) - if !ok { - tracer = log.NewNopTracer(app.logger) - } - span := tracer.StartSpan("runMsgs") + ctx, span := ctx.StartSpan(tracer, "runMsgs") defer span.End() events := sdk.EmptyEvents() @@ -1015,7 +1010,12 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) } - msgSpan := span.StartSpan("msgHandler", "msgType", sdk.MsgTypeURL(msg), "msgIndex", i) + ctx, msgSpan := ctx.StartSpan(tracer, "msgHandler", + trace.WithAttributes( + attribute.String("msg_type", sdk.MsgTypeURL(msg)), + attribute.Int("msg_index", i), + ), + ) // ADR 031 request type routing msgResult, err := handler(ctx, msg) if err != nil { @@ -1110,7 +1110,7 @@ func (app *BaseApp) PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) { return nil, err } - _, _, _, err = app.RunTx(execModePrepareProposal, bz, tx, -1, nil, nil, app.tracer) + _, _, _, err = app.RunTx(execModePrepareProposal, bz, tx, -1, nil, nil) if err != nil { return nil, err } @@ -1129,7 +1129,7 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) { return nil, err } - _, _, _, err = app.RunTx(execModeProcessProposal, txBz, tx, -1, nil, nil, app.tracer) + _, _, _, err = app.RunTx(execModeProcessProposal, txBz, tx, -1, nil, nil) if err != nil { return nil, err } diff --git a/baseapp/genesis.go b/baseapp/genesis.go index cbc1adc5d709..547a1322d8a7 100644 --- a/baseapp/genesis.go +++ b/baseapp/genesis.go @@ -13,7 +13,7 @@ var _ genesis.TxHandler = (*BaseApp)(nil) // ExecuteGenesisTx implements genesis.GenesisState from // cosmossdk.io/core/genesis to set initial state in genesis func (ba *BaseApp) ExecuteGenesisTx(tx []byte) error { - res := ba.deliverTx(tx, nil, -1, nil, ba.tracer) + res := ba.deliverTx(tx, nil, -1, nil) if res.Code != types.CodeTypeOK { return errors.New(res.Log) diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index f303bb4d8efa..6144552490b2 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -5,7 +5,6 @@ import ( errorsmod "cosmossdk.io/errors" - "cosmossdk.io/log" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -20,26 +19,24 @@ func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, * return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeCheck, bz, tx, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) + gasInfo, result, _, err := app.RunTx(execModeCheck, bz, tx, -1, nil, nil) return gasInfo, result, err } // Simulate executes a tx in simulate mode to get result and gas info. func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { - gasInfo, result, _, err := app.RunTx(execModeSimulate, txBytes, nil, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) + gasInfo, result, _, err := app.RunTx(execModeSimulate, txBytes, nil, -1, nil, nil) return gasInfo, result, err } func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { - span := app.blockSpan.StartSpan("SimDeliver") - defer span.End() // See comment for Check(). bz, err := txEncoder(tx) if err != nil { return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, span) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil) return gasInfo, result, err } @@ -50,7 +47,7 @@ func (app *BaseApp) SimTxFinalizeBlock(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk. return sdk.GasInfo{}, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil, log.NewNopTracer(log.NewNopLogger())) + gasInfo, result, _, err := app.RunTx(execModeFinalize, bz, tx, -1, nil, nil) return gasInfo, result, err } diff --git a/baseapp/txnrunner/default.go b/baseapp/txnrunner/default.go index 01b9921fc512..d92f8099d4dd 100644 --- a/baseapp/txnrunner/default.go +++ b/baseapp/txnrunner/default.go @@ -7,24 +7,21 @@ import ( storetypes "cosmossdk.io/store/types" - "cosmossdk.io/log" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) var _ sdk.TxRunner = DefaultRunner{} -func NewDefaultRunner(txDecoder sdk.TxDecoder, tracer log.Tracer) *DefaultRunner { +func NewDefaultRunner(txDecoder sdk.TxDecoder) *DefaultRunner { return &DefaultRunner{ txDecoder: txDecoder, - tracer: tracer, } } // DefaultRunner is the default TxnRunner implementation which executes the transactions in a block sequentially. type DefaultRunner struct { txDecoder sdk.TxDecoder - tracer log.Tracer } func (d DefaultRunner) Run(ctx context.Context, _ storetypes.MultiStore, txs [][]byte, deliverTx sdk.DeliverTxFunc) ([]*abci.ExecTxResult, error) { @@ -34,7 +31,7 @@ func (d DefaultRunner) Run(ctx context.Context, _ storetypes.MultiStore, txs [][ var response *abci.ExecTxResult if _, err := d.txDecoder(rawTx); err == nil { - response = deliverTx(rawTx, nil, i, nil, d.tracer) + response = deliverTx(rawTx, nil, i, nil) } else { // In the case where a transaction included in a block proposal is malformed, // we still want to return a default response to comet. This is because comet diff --git a/blockstm/executor.go b/blockstm/executor.go index 8ee790c486b9..4014c4ead966 100644 --- a/blockstm/executor.go +++ b/blockstm/executor.go @@ -3,8 +3,6 @@ package blockstm import ( "context" "fmt" - - "cosmossdk.io/log" ) // Executor fields are not mutated during execution. @@ -86,6 +84,6 @@ func (e *Executor) NeedsReexecution(version TxnVersion) (TxnVersion, TaskKind) { func (e *Executor) execute(txn TxnIndex) *MultiMVMemoryView { view := e.mvMemory.View(txn) - e.txExecutor(txn, view, log.TracerFromContext(e.ctx)) + e.txExecutor(txn, view) return view } diff --git a/blockstm/txnrunner.go b/blockstm/txnrunner.go index eb3461e93a8a..9e50c60522ea 100644 --- a/blockstm/txnrunner.go +++ b/blockstm/txnrunner.go @@ -10,7 +10,6 @@ import ( "cosmossdk.io/collections" storetypes "cosmossdk.io/store/types" - "cosmossdk.io/log" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -79,7 +78,7 @@ func (e STMRunner) Run(ctx context.Context, ms storetypes.MultiStore, txs [][]by stmMultiStoreWrapper{ms}, e.workers, estimates, - func(txn TxnIndex, ms MultiStore, tracer log.Tracer) { + func(txn TxnIndex, ms MultiStore) { var cache map[string]any // only one of the concurrent incarnations gets the cache if there are any, otherwise execute without @@ -93,7 +92,7 @@ func (e STMRunner) Run(ctx context.Context, ms storetypes.MultiStore, txs [][]by if memTxs != nil { memTx = memTxs[txn] } - results[txn] = deliverTx(memTx, msWrapper{ms}, int(txn), cache, tracer) + results[txn] = deliverTx(memTx, msWrapper{ms}, int(txn), cache) if v != nil { incarnationCache[txn].Store(v) diff --git a/blockstm/types.go b/blockstm/types.go index fa533dfdfefc..873647b2f077 100644 --- a/blockstm/types.go +++ b/blockstm/types.go @@ -2,8 +2,6 @@ package blockstm import ( storetypes "cosmossdk.io/store/types" - - "cosmossdk.io/log" ) const ( @@ -61,7 +59,7 @@ type ReadSet struct { type MultiReadSet = map[int]*ReadSet // TxExecutor executes transactions on top of a multi-version memory view. -type TxExecutor func(TxnIndex, MultiStore, log.Tracer) +type TxExecutor func(TxnIndex, MultiStore) type MultiStore interface { GetStore(storetypes.StoreKey) storetypes.Store diff --git a/client/v2/go.mod b/client/v2/go.mod index 7d46f2c4ca34..1f1aeb58a265 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -30,7 +30,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect @@ -88,6 +87,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect @@ -126,7 +126,8 @@ require ( github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect @@ -148,13 +149,24 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/mock v0.6.0 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index 58676ed6c45a..a26eb6a85ec5 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -29,7 +29,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -348,6 +347,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -602,6 +603,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -609,8 +612,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -718,20 +721,42 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= diff --git a/go.mod b/go.mod index c53a80266aad..9f4d8319c8ae 100644 --- a/go.mod +++ b/go.mod @@ -46,8 +46,6 @@ require ( github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-isatty v0.0.20 github.com/mdp/qrterminal/v3 v3.2.1 - github.com/prometheus/client_golang v1.23.2 - github.com/prometheus/common v0.67.1 github.com/rs/zerolog v1.34.0 github.com/spf13/cast v1.10.0 github.com/spf13/cobra v1.10.1 @@ -57,12 +55,8 @@ require ( github.com/tendermint/go-amino v0.16.0 github.com/test-go/testify v1.1.4 github.com/tidwall/btree v1.8.1 + go.opentelemetry.io/contrib/otelconf v0.18.0 go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 - go.opentelemetry.io/otel/sdk v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/mock v0.6.0 golang.org/x/crypto v0.43.0 @@ -87,7 +81,6 @@ require ( cosmossdk.io/schema v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect @@ -194,7 +187,9 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.1 // indirect github.com/prometheus/otlptranslator v0.0.2 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect @@ -219,16 +214,20 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect - go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect diff --git a/go.sum b/go.sum index 0df3549e96fa..a090e17f9037 100644 --- a/go.sum +++ b/go.sum @@ -49,7 +49,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -717,8 +716,6 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -859,8 +856,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= @@ -869,8 +864,6 @@ go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PD go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= @@ -883,6 +876,8 @@ go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5 go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= diff --git a/log/tracer.go b/log/tracer.go deleted file mode 100644 index ac304b1a5e41..000000000000 --- a/log/tracer.go +++ /dev/null @@ -1,99 +0,0 @@ -package log - -import "context" - -// Tracer is an interface for creating and managing spans. -// It may be backed by open telemetry or other tracing libraries, -// Spans may also be used for collecting timing metrics. -// It embeds the Logger interface. Log events may be associated with spans. -type Tracer interface { - Logger - - // StartSpan starts a new span with the given operation name and key-value pair attributes. - // If there is a parent span, the new span will be a child of that span. - // It is recommended to use a defer statement to end the span like this: - // span := tracer.StartSpan("my-span") - // defer span.End() - StartSpan(operation string, kvs ...any) Span - - // StartSpanContext attempts to retrieve an existing tracer from the context and then starts a new span - // as a child of that span. - // If no tracer is found, it returns a new span that is a child of this tracer instance. - // This is useful if a span may have been set in the context, but we are not sure. - // The function also returns a context with the span added to it. - StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, Span) - - // StartRootSpan returns a root-level span that doesn't have a parent. - // Use this when starting async work to ensure its spans are not timed as part of the current span. - // Example usage: - // go func() { - // ctx, span := outerSpan.StartRootSpan(ctx, "my-go-routine") - // defer span.End() - // doSomething() - // }() - StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, Span) -} - -// Span is an interface for managing spans and creating nested spans via the embedded Tracer interface. -type Span interface { - // Tracer is embedded to allow for the creation of nested spans. - Tracer - - // SetAttrs sets additional key-value attributes on the span. - SetAttrs(kvs ...any) - - // SetErr records an optional error on the span and optionally adds additional key-value pair attributes. - // It returns the error value unchanged, allowing use in return statements. - // If err is nil, the span is marked as successful. - // If err is not nil, the span is marked as failed. - // This does NOT end the span, you must still call End. - // Example usage: - // span := tracer.StartSpan("my-span") - // defer span.End() - // err := doSomething() - // return span.SetErr(err, "additional", "info") // okay to call with a nil error - SetErr(err error, kvs ...any) error - - // End marks the end of a span and is designed to be used in a defer statement right after the span is created. - // Calling End on a span that has already ended is a no-op. - // Example usage: - // span := tracer.StartSpan("my-span") - // defer span.End() - End() -} - -// NewNopTracer returns a Tracer that wraps a logger for logging methods but does not emit any spans. -func NewNopTracer(logger Logger) Tracer { - return nopTracer{ - Logger: logger, - } -} - -type nopTracer struct { - Logger -} - -func (n nopTracer) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, Span) { - return ctx, nopSpan{n} -} - -func (n nopTracer) StartSpanContext(ctx context.Context, _ string, _ ...any) (context.Context, Span) { - return ctx, nopSpan{n} -} - -func (n nopTracer) StartSpan(string, ...any) Span { - return nopSpan{n} -} - -type nopSpan struct { - nopTracer -} - -func (n nopSpan) SetAttrs(...any) {} - -func (n nopSpan) SetErr(err error, _ ...any) error { return err } - -func (n nopSpan) End() {} - -var _ Tracer = nopTracer{} -var _ Span = nopSpan{} diff --git a/server/api/server.go b/server/api/server.go index 0aa1cf170956..3cb276871cec 100644 --- a/server/api/server.go +++ b/server/api/server.go @@ -2,7 +2,6 @@ package api import ( "context" - "fmt" "net" "net/http" "strings" @@ -24,7 +23,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/server/config" cmtlogwrapper "github.com/cosmos/cosmos-sdk/server/log" - "github.com/cosmos/cosmos-sdk/telemetry" grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" ) @@ -35,7 +33,6 @@ type Server struct { ClientCtx client.Context GRPCSrv *grpc.Server logger log.Logger - metrics *telemetry.Metrics // Start() is blocking and generally called from a separate goroutine. // Close() can be called asynchronously and access shared memory @@ -191,31 +188,6 @@ func (s *Server) Close() error { return s.listener.Close() } -func (s *Server) SetTelemetry(m *telemetry.Metrics) { - s.mtx.Lock() - s.registerMetrics(m) - s.mtx.Unlock() -} - -func (s *Server) registerMetrics(m *telemetry.Metrics) { - s.metrics = m - - metricsHandler := func(w http.ResponseWriter, r *http.Request) { - format := strings.TrimSpace(r.FormValue("format")) - - gr, err := s.metrics.Gather(format) - if err != nil { - writeErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err)) - return - } - - w.Header().Set("Content-Type", gr.ContentType) - _, _ = w.Write(gr.Metrics) - } - - s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET") -} - // errorResponse defines the attributes of a JSON error response. type errorResponse struct { Code int `json:"code,omitempty"` diff --git a/server/config/config.go b/server/config/config.go index a50893e850c1..c9fa4351a087 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -8,7 +8,7 @@ import ( pruningtypes "cosmossdk.io/store/pruning/types" - "github.com/cosmos/cosmos-sdk/telemetry" + _ "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -190,13 +190,12 @@ type Config struct { BaseConfig `mapstructure:",squash"` // Telemetry defines the application telemetry configuration - Telemetry telemetry.Config `mapstructure:"telemetry"` - API APIConfig `mapstructure:"api"` - GRPC GRPCConfig `mapstructure:"grpc"` - GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` - StateSync StateSyncConfig `mapstructure:"state-sync"` - Streaming StreamingConfig `mapstructure:"streaming"` - Mempool MempoolConfig `mapstructure:"mempool"` + API APIConfig `mapstructure:"api"` + GRPC GRPCConfig `mapstructure:"grpc"` + GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` + StateSync StateSyncConfig `mapstructure:"state-sync"` + Streaming StreamingConfig `mapstructure:"streaming"` + Mempool MempoolConfig `mapstructure:"mempool"` } // SetMinGasPrices sets the validator's minimum gas prices. @@ -234,10 +233,6 @@ func DefaultConfig() *Config { IAVLDisableFastNode: false, AppDBBackend: "", }, - Telemetry: telemetry.Config{ - Enabled: false, - GlobalLabels: [][]string{}, - }, API: APIConfig{ Enable: false, Swagger: false, diff --git a/server/start.go b/server/start.go index aab620b27548..4bb4c29b1e0d 100644 --- a/server/start.go +++ b/server/start.go @@ -230,20 +230,15 @@ func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreato } defer appCleanupFn() - metrics, err := startTelemetry(svrCfg) - if err != nil { - return fmt.Errorf("failed to start telemetry: %w", err) - } - emitServerInfoMetrics() if !withCmt { - return startStandAlone(svrCtx, svrCfg, clientCtx, app, metrics, opts) + return startStandAlone(svrCtx, svrCfg, clientCtx, app, opts) } - return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts) + return startInProcess(svrCtx, svrCfg, clientCtx, app, opts) } -func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, metrics *telemetry.Metrics, opts StartCmdOptions) error { +func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, opts StartCmdOptions) error { addr := svrCtx.Viper.GetString(flagAddress) transport := svrCtx.Viper.GetString(flagTransport) @@ -282,7 +277,7 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie return err } - err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, svrCtx.Config.RootDir, grpcSrv, metrics) + err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, svrCtx.Config.RootDir, grpcSrv) if err != nil { return err } @@ -309,9 +304,7 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie return g.Wait() } -func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, - metrics *telemetry.Metrics, opts StartCmdOptions, -) error { +func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, opts StartCmdOptions) error { cmtCfg := svrCtx.Config gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly) @@ -348,7 +341,7 @@ func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clien return fmt.Errorf("failed to start grpc server: %w", err) } - err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, cmtCfg.RootDir, grpcSrv, metrics) + err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, cmtCfg.RootDir, grpcSrv) if err != nil { return fmt.Errorf("failed to start api server: %w", err) } @@ -517,7 +510,6 @@ func startAPIServer( app types.Application, home string, grpcSrv *grpc.Server, - metrics *telemetry.Metrics, ) error { if !svrCfg.API.Enable { return nil @@ -528,20 +520,12 @@ func startAPIServer( apiSrv := api.New(clientCtx, svrCtx.Logger.With("module", "api-server"), grpcSrv) app.RegisterAPIRoutes(apiSrv, svrCfg.API) - if svrCfg.Telemetry.Enabled { - apiSrv.SetTelemetry(metrics) - } - g.Go(func() error { return apiSrv.Start(ctx, svrCfg) }) return nil } -func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) { - return telemetry.New(cfg.Telemetry) -} - // wrapCPUProfile starts CPU profiling, if enabled, and executes the provided // callbackFn in a separate goroutine, then will wait for that callback to // return. diff --git a/simapp/go.mod b/simapp/go.mod index 167548375072..f3b7f18f7733 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -41,7 +41,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect @@ -132,6 +131,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect @@ -177,7 +177,8 @@ require ( github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect @@ -203,13 +204,23 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index 462724459d82..016a7cbf300d 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -53,7 +53,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -449,6 +448,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -716,6 +717,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -723,8 +726,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -847,22 +850,42 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 848911ef2ffd..ae9607fc386f 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -3,6 +3,7 @@ package simapp import ( + "context" "encoding/binary" "encoding/json" "flag" @@ -17,6 +18,7 @@ import ( dbm "github.com/cosmos/cosmos-db" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/telemetry" @@ -159,6 +161,10 @@ func IsEmptyValidatorSetErr(err error) bool { } func TestAppStateDeterminism(t *testing.T) { + telemetry.TestingInit(t, context.Background()) + tracer := otel.Tracer("test") + _, span := tracer.Start(context.Background(), "TestAppStateDeterminism") + defer span.End() const numTimesToRunPerSeed = 1 var seeds []int64 if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue { @@ -191,10 +197,7 @@ func TestAppStateDeterminism(t *testing.T) { return others.Get(k) }) } - metrics := telemetry.TestingInit(t, nil, logger) - baseAppSpan := metrics.Tracer().StartSpan("baseapp") - t.Cleanup(baseAppSpan.End) - return NewSimApp(baseAppSpan, db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...) + return NewSimApp(logger, db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...) } var mx sync.Mutex appHashResults := make(map[int64][][]byte) diff --git a/systemtests/go.mod b/systemtests/go.mod index 8fd0c16fb18a..9affc6f367c3 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -26,7 +26,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect @@ -85,6 +84,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect @@ -122,7 +122,8 @@ require ( github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect @@ -148,13 +149,24 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index ae9b36f2df21..7e5dfcf655aa 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -31,7 +31,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -348,6 +347,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -602,6 +603,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -609,8 +612,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -727,20 +730,42 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= diff --git a/telemetry/config.go b/telemetry/config.go index 42b78413e73e..440b7002227d 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -1,172 +1,58 @@ package telemetry import ( - "net/http" + "context" + "encoding/json" + "fmt" + "os" - "github.com/hashicorp/go-metrics" - "github.com/prometheus/common/expfmt" - otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0" + "go.opentelemetry.io/contrib/otelconf/v0.3.0" ) -// globalTelemetryEnabled is a private variable that stores the telemetry enabled state. -// It is set on initialization and does not change for the lifetime of the program. -var globalTelemetryEnabled bool +var sdk otelconf.SDK -// IsTelemetryEnabled provides controlled access to check if telemetry is enabled. -func IsTelemetryEnabled() bool { - return globalTelemetryEnabled -} - -// EnableTelemetry allows for the global telemetry enabled state to be set. -func EnableTelemetry() { - globalTelemetryEnabled = true -} - -// globalLabels defines the set of global labels that will be applied to all -// metrics emitted using the telemetry package function wrappers. -var globalLabels = []metrics.Label{} - -// Metrics supported format types. -const ( - FormatDefault = "" - FormatPrometheus = "prometheus" - FormatText = "text" - ContentTypeText = `text/plain; version=` + expfmt.TextVersion + `; charset=utf-8` - - MetricSinkInMem = "mem" - MetricSinkStatsd = "statsd" - MetricSinkDogsStatsd = "dogstatsd" - MetricSinkFile = "file" - - TraceSinkOtel = "otel" - TraceSinkNoop = "noop" -) - -// DisplayableSink is an interface that defines a method for displaying metrics. -type DisplayableSink interface { - DisplayMetrics(resp http.ResponseWriter, req *http.Request) (any, error) -} - -// Config defines the configuration options for application telemetry. -type Config struct { - // ServiceName is the identifier for this service, used as a prefix for all metric keys. - // Example: "cosmos-app" → metrics like "cosmos-app.tx.count" - ServiceName string `mapstructure:"service-name" json:"service-name"` - - // Enabled controls whether telemetry is active. When false, all telemetry operations - // become no-ops with zero overhead. When true, metrics collection is activated. - Enabled bool `mapstructure:"enabled" json:"enabled"` +var isTelemetryEnabled = true - // EnableHostname prefixes gauge values with the hostname. - // Useful in multi-node deployments to identify which node emitted a metric. - EnableHostname bool `mapstructure:"enable-hostname" json:"enable-hostname"` +func init() { + var err error - // EnableHostnameLabel adds a "hostname" label to all metrics. - // Alternative to EnableHostname that works better with label-based systems like Prometheus. - EnableHostnameLabel bool `mapstructure:"enable-hostname-label" json:"enable-hostname-label"` + var opts []otelconf.ConfigurationOption - // EnableServiceLabel adds a "service" label with the ServiceName to all metrics. - // Useful when aggregating metrics from multiple services in one monitoring system. - EnableServiceLabel bool `mapstructure:"enable-service-label" json:"enable-service-label"` + confFilename := os.Getenv("COSMOS_TELEMETRY") + if confFilename != "" { + bz, err := os.ReadFile(confFilename) + if err != nil { + panic(fmt.Sprintf("failed to read telemetry config file: %v", err)) + } - // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. - // Defines how long (in seconds) metrics are retained in memory for scraping. - // The Prometheus sink is added to a FanoutSink alongside the primary sink. - // Recommended value: 60 seconds or more. - PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time" json:"prometheus-retention-time"` + cfg, err := otelconf.ParseYAML(bz) + if err != nil { + panic(fmt.Sprintf("failed to parse telemetry config file: %v", err)) + } - // GlobalLabels defines a set of key-value label pairs applied to ALL metrics. - // These labels are automatically attached to every metric emission. - // Useful for static identifiers like chain ID, environment, region, etc. - // - // Example: [][]string{{"chain_id", "cosmoshub-1"}, {"env", "production"}} - // - // Note: The outer array contains label pairs, each inner array has exactly 2 elements [key, value]. - GlobalLabels [][]string `mapstructure:"global-labels" json:"global-labels"` + cfgJson, err := json.Marshal(cfg) + if err != nil { + panic(fmt.Sprintf("failed to marshal telemetry config file: %v", err)) + } + fmt.Printf("\nInitializing telemetry with config:\n%s\n\n", cfgJson) - // MetricsSink defines the metrics backend type. Supported values: - // - "mem" (default): In-memory sink with SIGUSR1 dump-to-stderr capability - // - "prometheus": Prometheus exposition format (use with PrometheusRetentionTime) - // - "statsd": StatsD protocol (push-based, requires StatsdAddr) - // - "dogstatsd": Datadog-enhanced StatsD with tags (requires StatsdAddr, DatadogHostname) - // - "file": JSON lines written to a file (requires MetricsFile) - // - // Multiple sinks can be active via FanoutSink (e.g., mem + prometheus). - MetricsSink string `mapstructure:"metrics-sink" json:"metrics-sink" default:"mem"` + opts = append(opts, otelconf.WithOpenTelemetryConfiguration(*cfg)) + } - // StatsdAddr is the address of the StatsD or DogStatsD server (host:port). - // Only used when MetricsSink is "statsd" or "dogstatsd". - // Example: "localhost:8125" - StatsdAddr string `mapstructure:"statsd-addr" json:"statsd-addr"` - - // DatadogHostname is the hostname to report when using DogStatsD. - // Only used when MetricsSink is "dogstatsd". - // If empty, the system hostname is used. - DatadogHostname string `mapstructure:"datadog-hostname" json:"datadog-hostname"` - - // MetricsFile is the file path to write metrics to in JSONL format. - // Only used when MetricsSink is "file". - // Each metric emission creates a JSON line: {"timestamp":"...","type":"counter","key":[...],"value":1.0} - // Example: "/tmp/metrics.jsonl" or "./metrics.jsonl" - MetricsFile string `mapstructure:"metrics-file" json:"metrics-file"` - - // TraceSink is the sink for trace data. Supported values: - // - "otel": OpenTelemetry trace sink - // - "metrics": all spans will be redirected to emit invocation counter and timing histogram metrics - // - "noop": No-op trace sink (default) - TraceSink string `mapstructure:"trace-sink" json:"trace-sink"` - - // OtelTraceExporters is a list of exporters to use for trace data. - // This is only used when trace sink is set to "otel". - OtelTraceExporters []OtelExportConfig `mapstructure:"otel-trace-exporters" json:"otel-trace-exporters"` - - // OtelMetricsExporters is a list of exporters to use for metrics data via OpenTelemetry. - // When configured, wrapper functions (IncrCounter, SetGauge, etc.) will use OTel instruments - // instead of go-metrics, and metrics will be exported through these exporters. - OtelMetricsExporters []OtelExportConfig `mapstructure:"otel-metrics-exporters" json:"otel-metrics-exporters"` + sdk, err = otelconf.NewSDK(opts...) + if err != nil { + panic(fmt.Sprintf("failed to initialize telemetry: %v", err)) + } } -// OtelExportConfig defines configuration for an OpenTelemetry exporter. -// This is used for both traces and metrics exporters. -type OtelExportConfig struct { - // Type is the exporter type. - // For traces: "stdout", "otlp" - // For metrics: "stdout", "otlp", "prometheus" - Type string `mapstructure:"type" json:"type"` - - // OTLPTransport is the transport protocol to use for OTLP. - // Must be one of: - // - "http" (default) - // - "grpc" - // Only used when Type is "otlp". - OTLPTransport string `mapstructure:"otlp-transport" json:"otlp-transport"` - - // Endpoint is the OTLP exporter endpoint URL (grpc or http). - // Only used when Type is "otlp". - // Example: "localhost:4318" for HTTP, "localhost:4317" for gRPC - Endpoint string `mapstructure:"endpoint" json:"endpoint"` - - // Insecure disables TLS certificate verification for OTLP exporters. - // Only used when Type is "otlp". - Insecure bool `mapstructure:"insecure" json:"insecure"` - - // Headers is a map of HTTP headers to send with each request to the OTLP exporter. - // Useful for authentication, authorization, or custom metadata. - // Only used when Type is "otlp". - // Example: {"Authorization": "Bearer token123", "X-Custom-Header": "value"} - Headers map[string]string `mapstructure:"headers" json:"headers"` - - // File is the file path to write data to when using the "stdout" exporter. - // If empty, data is written to stdout. - // Only used when Type is "stdout". - File string `mapstructure:"file" json:"file"` +func Shutdown(ctx context.Context) error { + return sdk.Shutdown(ctx) +} - // PrettyPrint enables pretty-printing of JSON output when using the "stdout" exporter. - // Only used when Type is "stdout" for traces. - PrettyPrint bool `mapstructure:"pretty-print" json:"pretty-print"` +func IsTelemetryEnabled() bool { + return isTelemetryEnabled +} - // ListenAddress is the address to listen on for Prometheus scraping. - // Only used when Type is "prometheus" for metrics. - // Example: ":8080" or "localhost:9090" - ListenAddress string `mapstructure:"listen-address" json:"listen-address"` +func SetTelemetryEnabled(v bool) { + isTelemetryEnabled = v } diff --git a/telemetry/doc.go b/telemetry/doc.go index 98afc159ba42..7019bd9a9563 100644 --- a/telemetry/doc.go +++ b/telemetry/doc.go @@ -1,39 +1,2 @@ -// Package telemetry provides observability through metrics and distributed tracing. -// -// # Metrics Collection -// -// Metrics collection uses hashicorp/go-metrics with support for multiple sink backends: -// - mem: In-memory aggregation with SIGUSR1 signal dumping to stderr -// - prometheus: Prometheus registry for pull-based scraping via /metrics endpoint -// - statsd: Push-based metrics to StatsD daemon -// - dogstatsd: Push-based metrics to Datadog StatsD daemon with tagging -// - file: Write metrics to a file as JSON lines (useful for tests and debugging) -// -// Multiple sinks can be active simultaneously via FanoutSink (e.g., both in-memory and Prometheus). -// -// # Distributed Tracing -// -// Tracing support is provided via OtelSpan, which wraps OpenTelemetry for hierarchical span tracking. -// See otel.go for the log.Tracer implementation. -// -// # Usage -// -// Initialize metrics at application startup: -// -// m, err := telemetry.New(telemetry.Config{ -// Enabled: true, -// ServiceName: "cosmos-app", -// PrometheusRetentionTime: 60, -// GlobalLabels: [][]string{{"chain_id", "cosmoshub-1"}}, -// }) -// if err != nil { -// log.Fatal(err) -// } -// defer m.Close() -// -// Emit metrics from anywhere in the application: -// -// telemetry.IncrCounter(1, "tx", "processed") -// telemetry.SetGauge(1024, "mempool", "size") -// defer telemetry.MeasureSince(telemetry.Now(), "block", "execution") +// Package telemetry initialize OpenTelemetry and provides metrics wrapper functions. package telemetry diff --git a/telemetry/file_sink.go b/telemetry/file_sink.go deleted file mode 100644 index 4c653558fd6d..000000000000 --- a/telemetry/file_sink.go +++ /dev/null @@ -1,157 +0,0 @@ -package telemetry - -import ( - "bufio" - "encoding/json" - "fmt" - "io" - "sync" - "time" - - "github.com/hashicorp/go-metrics" -) - -// FileSink writes metrics to a file as JSON lines (JSONL format). -// Each metric emission creates a single JSON line with timestamp, type, key, value, and labels. -// -// This sink is particularly useful for: -// - Test environments where metrics need to be inspected after execution -// - CI/CD pipelines where metrics should be logged for analysis -// - Debugging and local development -// -// The sink is thread-safe and buffers writes for performance. -// Call Close() to flush buffered data and close the underlying writer. -type FileSink struct { - writer *bufio.Writer - closer io.Closer - mu sync.Mutex - closed bool -} - -// metricLine represents a single metric emission in JSON format. -type metricLine struct { - Timestamp time.Time `json:"timestamp"` - Type string `json:"type"` - Key []string `json:"key"` - Value float32 `json:"value"` - Labels []metrics.Label `json:"labels,omitempty"` -} - -// NewFileSink creates a new FileSink that writes to the given io.WriteCloser. -// The sink buffers writes for performance. Call Close() to flush and close. -func NewFileSink(w io.WriteCloser) *FileSink { - return &FileSink{ - writer: bufio.NewWriter(w), - closer: w, - closed: false, - } -} - -// SetGauge implements metrics.MetricSink. -func (f *FileSink) SetGauge(key []string, val float32) { - f.SetGaugeWithLabels(key, val, nil) -} - -// SetGaugeWithLabels implements metrics.MetricSink. -func (f *FileSink) SetGaugeWithLabels(key []string, val float32, labels []metrics.Label) { - f.writeLine(metricLine{ - Timestamp: time.Now().UTC(), - Type: "gauge", - Key: key, - Value: val, - Labels: labels, - }) -} - -// EmitKey implements metrics.MetricSink. -func (f *FileSink) EmitKey(key []string, val float32) { - f.writeLine(metricLine{ - Timestamp: time.Now().UTC(), - Type: "kv", - Key: key, - Value: val, - }) -} - -// IncrCounter implements metrics.MetricSink. -func (f *FileSink) IncrCounter(key []string, val float32) { - f.IncrCounterWithLabels(key, val, nil) -} - -// IncrCounterWithLabels implements metrics.MetricSink. -func (f *FileSink) IncrCounterWithLabels(key []string, val float32, labels []metrics.Label) { - f.writeLine(metricLine{ - Timestamp: time.Now().UTC(), - Type: "counter", - Key: key, - Value: val, - Labels: labels, - }) -} - -// AddSample implements metrics.MetricSink. -func (f *FileSink) AddSample(key []string, val float32) { - f.AddSampleWithLabels(key, val, nil) -} - -// AddSampleWithLabels implements metrics.MetricSink. -func (f *FileSink) AddSampleWithLabels(key []string, val float32, labels []metrics.Label) { - f.writeLine(metricLine{ - Timestamp: time.Now().UTC(), - Type: "sample", - Key: key, - Value: val, - Labels: labels, - }) -} - -// writeLine writes a metric line to the file as JSON. -func (f *FileSink) writeLine(line metricLine) { - f.mu.Lock() - defer f.mu.Unlock() - - if f.closed { - return - } - - data, err := json.Marshal(line) - if err != nil { - // If JSON marshaling fails, write error to stderr but don't crash - fmt.Fprintf(io.Discard, "failed to marshal metric: %v\n", err) - return - } - - // Write JSON line with newline - if _, err := f.writer.Write(data); err != nil { - return - } - if err := f.writer.WriteByte('\n'); err != nil { - return - } -} - -// Close flushes any buffered data and closes the underlying writer. -// It is safe to call Close multiple times. -func (f *FileSink) Close() error { - f.mu.Lock() - defer f.mu.Unlock() - - if f.closed { - return nil - } - - f.closed = true - - // Flush buffered data - if err := f.writer.Flush(); err != nil { - f.closer.Close() // Try to close anyway - return fmt.Errorf("failed to flush metrics file: %w", err) - } - - // Close the underlying file - if err := f.closer.Close(); err != nil { - return fmt.Errorf("failed to close metrics file: %w", err) - } - - return nil -} diff --git a/telemetry/file_sink_test.go b/telemetry/file_sink_test.go deleted file mode 100644 index 4a8feceb7e37..000000000000 --- a/telemetry/file_sink_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package telemetry - -import ( - "bufio" - "encoding/json" - "os" - "path/filepath" - "sync" - "testing" - - "github.com/hashicorp/go-metrics" - "github.com/stretchr/testify/require" -) - -func TestFileSink_BasicOperations(t *testing.T) { - tmpfile := filepath.Join(t.TempDir(), "metrics.jsonl") - file, err := os.Create(tmpfile) - require.NoError(t, err) - - sink := NewFileSink(file) - - // Emit various metric types - sink.IncrCounter([]string{"test", "counter"}, 1.5) - sink.SetGauge([]string{"test", "gauge"}, 42.0) - sink.AddSample([]string{"test", "sample"}, 100.0) - sink.EmitKey([]string{"test", "kv"}, 3.14) - - // Emit metrics with labels - labels := []metrics.Label{ - {Name: "module", Value: "bank"}, - {Name: "operation", Value: "send"}, - } - sink.IncrCounterWithLabels([]string{"test", "counter_labeled"}, 2.0, labels) - sink.SetGaugeWithLabels([]string{"test", "gauge_labeled"}, 99.0, labels) - sink.AddSampleWithLabels([]string{"test", "sample_labeled"}, 50.0, labels) - - // Close to flush - require.NoError(t, sink.Close()) - - // Read and verify file contents - data, err := os.ReadFile(tmpfile) - require.NoError(t, err) - - file2, err := os.Open(tmpfile) - require.NoError(t, err) - defer file2.Close() - - scanner := bufio.NewScanner(file2) - lineCount := 0 - for scanner.Scan() { - lineCount++ - var metric metricLine - err := json.Unmarshal(scanner.Bytes(), &metric) - require.NoError(t, err, "line %d should be valid JSON", lineCount) - require.NotZero(t, metric.Timestamp, "line %d should have timestamp", lineCount) - require.NotEmpty(t, metric.Type, "line %d should have type", lineCount) - require.NotEmpty(t, metric.Key, "line %d should have key", lineCount) - } - require.NoError(t, scanner.Err()) - require.Equal(t, 7, lineCount, "should have 7 metrics") - - // Verify specific metric formats - require.Contains(t, string(data), `"type":"counter"`) - require.Contains(t, string(data), `"type":"gauge"`) - require.Contains(t, string(data), `"type":"sample"`) - require.Contains(t, string(data), `"type":"kv"`) - require.Contains(t, string(data), `"key":["test","counter"]`) - require.Contains(t, string(data), `"value":1.5`) - require.Contains(t, string(data), `"labels":[{"Name":"module","Value":"bank"}`) -} - -func TestFileSink_ConcurrentWrites(t *testing.T) { - tmpfile := filepath.Join(t.TempDir(), "metrics_concurrent.jsonl") - file, err := os.Create(tmpfile) - require.NoError(t, err) - - sink := NewFileSink(file) - - // Spawn multiple goroutines writing metrics concurrently - const numGoroutines = 10 - const metricsPerGoroutine = 100 - - var wg sync.WaitGroup - wg.Add(numGoroutines) - - for i := 0; i < numGoroutines; i++ { - go func(id int) { - defer wg.Done() - for j := 0; j < metricsPerGoroutine; j++ { - sink.IncrCounter([]string{"concurrent", "test"}, float32(id)) - } - }(i) - } - - wg.Wait() - require.NoError(t, sink.Close()) - - // Count lines in file - file, err = os.Open(tmpfile) - require.NoError(t, err) - defer file.Close() - - scanner := bufio.NewScanner(file) - lineCount := 0 - for scanner.Scan() { - lineCount++ - } - require.NoError(t, scanner.Err()) - require.Equal(t, numGoroutines*metricsPerGoroutine, lineCount, "all metrics should be written") -} - -func TestFileSink_CloseIdempotent(t *testing.T) { - tmpfile := filepath.Join(t.TempDir(), "metrics_close.jsonl") - file, err := os.Create(tmpfile) - require.NoError(t, err) - - sink := NewFileSink(file) - sink.IncrCounter([]string{"test"}, 1.0) - - // Close multiple times should not error - require.NoError(t, sink.Close()) - require.NoError(t, sink.Close()) - require.NoError(t, sink.Close()) -} - -func TestFileSink_WritesAfterClose(t *testing.T) { - tmpfile := filepath.Join(t.TempDir(), "metrics_after_close.jsonl") - file, err := os.Create(tmpfile) - require.NoError(t, err) - - sink := NewFileSink(file) - sink.IncrCounter([]string{"before"}, 1.0) - require.NoError(t, sink.Close()) - - // Writes after close should be silently ignored (no panic) - sink.IncrCounter([]string{"after"}, 1.0) - - // File should only contain one metric - data, err := os.ReadFile(tmpfile) - require.NoError(t, err) - - file3, err := os.Open(tmpfile) - require.NoError(t, err) - defer file3.Close() - - scanner := bufio.NewScanner(file3) - lineCount := 0 - for scanner.Scan() { - lineCount++ - } - require.NoError(t, scanner.Err()) - require.Equal(t, 1, lineCount, "only metric before close should be written") - require.Contains(t, string(data), `"key":["before"]`) - require.NotContains(t, string(data), `"key":["after"]`) -} - -func TestFileSink_JSONFormat(t *testing.T) { - tmpfile := filepath.Join(t.TempDir(), "metrics_json.jsonl") - file, err := os.Create(tmpfile) - require.NoError(t, err) - - sink := NewFileSink(file) - labels := []metrics.Label{{Name: "env", Value: "test"}} - sink.IncrCounterWithLabels([]string{"api", "requests"}, 5.0, labels) - require.NoError(t, sink.Close()) - - // Parse JSON and verify structure - data, err := os.ReadFile(tmpfile) - require.NoError(t, err) - - var metric metricLine - err = json.Unmarshal(data[:len(data)-1], &metric) // Remove trailing newline - require.NoError(t, err) - - require.Equal(t, "counter", metric.Type) - require.Equal(t, []string{"api", "requests"}, metric.Key) - require.Equal(t, float32(5.0), metric.Value) - require.Len(t, metric.Labels, 1) - require.Equal(t, "env", metric.Labels[0].Name) - require.Equal(t, "test", metric.Labels[0].Value) - require.NotZero(t, metric.Timestamp) -} diff --git a/telemetry/metrics.go b/telemetry/metrics.go deleted file mode 100644 index eb4491099e65..000000000000 --- a/telemetry/metrics.go +++ /dev/null @@ -1,366 +0,0 @@ -package telemetry - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "os" - "time" - - "github.com/hashicorp/go-metrics" - "github.com/hashicorp/go-metrics/datadog" - metricsprom "github.com/hashicorp/go-metrics/prometheus" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/expfmt" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" - "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" - "go.opentelemetry.io/otel/sdk/resource" - otelsdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.17.0" - - "cosmossdk.io/log" -) - -// Metrics provides access to the application's metrics collection system. -// It wraps the go-metrics global registry and configured sinks. -// -// When using the in-memory sink, sending SIGUSR1 to the process (kill -USR1 ) -// will dump current metrics to stderr for debugging. -// -// The Metrics object maintains references to configured sinks and provides -// a Gather() method for pull-based metric retrieval (useful for testing and monitoring). -// -// When using the file sink, call Close() to flush buffered data and close the file. -// -// Note: go-metrics uses a singleton global registry. Only one Metrics instance -// should be created per process. -type Metrics struct { - sink metrics.MetricSink - prometheusEnabled bool - startFuncs []func(ctx context.Context) error - shutdownFuncs []func(context.Context) error - logger log.Logger - tracer log.Tracer - metricsTracer *MetricsTracer - - // OpenTelemetry metrics (when OtelMetricsExporters is configured) - meterProvider *sdkmetric.MeterProvider - meter metric.Meter - - // Instrument cache for wrapper functions - counters map[string]metric.Int64Counter - gauges map[string]metric.Float64Gauge - histograms map[string]metric.Float64Histogram - mu sync.RWMutex -} - -// GatherResponse contains collected metrics in the requested format. -// The Metrics field holds the serialized metric data, and ContentType -// indicates how it's encoded ("application/json" or prometheus text format). -type GatherResponse struct { - Metrics []byte - ContentType string -} - -// New creates and initializes the metrics system with the given configuration. -// -// Returns nil if telemetry is disabled (cfg.Enabled == false), which allows -// callers to safely ignore the Metrics object. -// -// The function: -// - Initializes the go-metrics global registry -// - Configures the specified sink(s) (mem, prometheus, statsd, dogstatsd, file) -// - Sets up global labels to be applied to all metrics -// - Enables SIGUSR1 signal handling for in-memory sink dumps -// - Creates a FanoutSink if multiple sinks are needed (e.g., mem + prometheus) -// -// Example: -// -// m, err := telemetry.New(telemetry.Config{ -// Enabled: true, -// ServiceName: "cosmos-app", -// MetricsSink: telemetry.MetricSinkInMem, -// PrometheusRetentionTime: 60, -// }) -// if err != nil { -// return err -// } -// defer m.Close() -func New(cfg Config, opts ...Option) (_ *Metrics, rerr error) { - globalTelemetryEnabled = cfg.Enabled - if !cfg.Enabled { - return nil, nil - } - - if numGlobalLabels := len(cfg.GlobalLabels); numGlobalLabels > 0 { - parsedGlobalLabels := make([]metrics.Label, numGlobalLabels) - for i, gl := range cfg.GlobalLabels { - parsedGlobalLabels[i] = NewLabel(gl[0], gl[1]) - } - globalLabels = parsedGlobalLabels - } - - metricsConf := metrics.DefaultConfig(cfg.ServiceName) - metricsConf.EnableHostname = cfg.EnableHostname - metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel - - // Initialize Metrics struct early with default logger - m := &Metrics{ - logger: log.NewNopLogger(), // Default to nop logger - } - // Apply functional options to set logger - for _, opt := range opts { - opt(m) - } - - var err error - switch cfg.MetricsSink { - case MetricSinkStatsd: - m.sink, err = metrics.NewStatsdSink(cfg.StatsdAddr) - case MetricSinkDogsStatsd: - m.sink, err = datadog.NewDogStatsdSink(cfg.StatsdAddr, cfg.DatadogHostname) - case MetricSinkFile: - if cfg.MetricsFile == "" { - return nil, errors.New("metrics-file must be set when metrics-sink is 'file'") - } - file, err := os.OpenFile(cfg.MetricsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return nil, fmt.Errorf("failed to open metrics file: %w", err) - } - fileSink := NewFileSink(file) - m.shutdownFuncs = append(m.shutdownFuncs, func(ctx context.Context) error { - return file.Close() - }) - m.sink = fileSink - default: - memSink := metrics.NewInmemSink(10*time.Second, time.Minute) - m.sink = memSink - inMemSig := metrics.DefaultInmemSignal(memSink) - defer func() { - if rerr != nil { - inMemSig.Stop() - } - }() - } - if err != nil { - return nil, err - } - - m.tracer = log.NewNopTracer(m.logger) - m.metricsTracer = NewMetricsTracer(metrics.Default(), nil, globalLabels, m.logger) - - switch cfg.TraceSink { - case TraceSinkOtel: - var tracerProviderOpts []otelsdktrace.TracerProviderOption - for _, exporterOpts := range cfg.OtelTraceExporters { - switch exporterOpts.Type { - case "otlp": - endpoint := exporterOpts.Endpoint - if endpoint == "" { - return nil, fmt.Errorf("otlp endpoint must be set") - } - var client otlptrace.Client - switch exporterOpts.OTLPTransport { - case "grpc": - opts := []otlptracegrpc.Option{otlptracegrpc.WithEndpoint(endpoint)} - if exporterOpts.Insecure { - opts = append(opts, otlptracegrpc.WithInsecure()) - } - if len(exporterOpts.Headers) > 0 { - opts = append(opts, otlptracegrpc.WithHeaders(exporterOpts.Headers)) - } - client = otlptracegrpc.NewClient(opts...) - default: - opts := []otlptracehttp.Option{otlptracehttp.WithEndpoint(endpoint)} - if exporterOpts.Insecure { - opts = append(opts, otlptracehttp.WithInsecure()) - } - if len(exporterOpts.Headers) > 0 { - opts = append(opts, otlptracehttp.WithHeaders(exporterOpts.Headers)) - } - client = otlptracehttp.NewClient(opts...) - } - exporter := otlptrace.NewUnstarted(client) - m.startFuncs = append(m.startFuncs, exporter.Start) - batcherOpt := otelsdktrace.WithBatcher(exporter) - tracerProviderOpts = append(tracerProviderOpts, batcherOpt) - case "stdout": - var opts []stdouttrace.Option - if exporterOpts.File != "" { - file, err := os.OpenFile(exporterOpts.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return nil, fmt.Errorf("failed to open stdout trace file %s: %w", exporterOpts.File, err) - } - opts = append(opts, stdouttrace.WithWriter(file)) - m.shutdownFuncs = append(m.shutdownFuncs, func(ctx context.Context) error { - return file.Close() - }) - } - if exporterOpts.PrettyPrint { - opts = append(opts, stdouttrace.WithPrettyPrint()) - } - exporter, err := stdouttrace.New(opts...) - if err != nil { - return nil, fmt.Errorf("failed to create stdout trace exporter: %w", err) - } - batcher := otelsdktrace.WithBatcher(exporter) - tracerProviderOpts = append(tracerProviderOpts, batcher) - default: - return nil, fmt.Errorf("unknown trace exporter type: %s", exporterOpts.Type) - } - } - - // Determine service name for both resource and tracer - serviceName := cfg.ServiceName - if serviceName == "" { - serviceName = "cosmos-sdk" - } - - // Create OpenTelemetry resource with service name - res, err := resource.New(context.Background(), - resource.WithAttributes( - semconv.ServiceName(serviceName), - ), - ) - if err != nil { - return nil, fmt.Errorf("failed to create trace resource: %w", err) - } - - // Add resource to tracer provider options - tracerProviderOpts = append(tracerProviderOpts, otelsdktrace.WithResource(res)) - tracerProvider := otelsdktrace.NewTracerProvider(tracerProviderOpts...) - m.shutdownFuncs = append(m.shutdownFuncs, tracerProvider.Shutdown) - m.tracer = NewOtelTracer(tracerProvider.Tracer(serviceName), m.logger) - case "metrics": - m.tracer = m.metricsTracer - default: - } - - fanout := metrics.FanoutSink{m.sink} - - if cfg.PrometheusRetentionTime > 0 { - m.prometheusEnabled = true - prometheusOpts := metricsprom.PrometheusOpts{ - Expiration: time.Duration(cfg.PrometheusRetentionTime) * time.Second, - } - - promSink, err := metricsprom.NewPrometheusSinkFrom(prometheusOpts) - if err != nil { - return nil, err - } - - fanout = append(fanout, promSink) - } - - if _, err := metrics.NewGlobal(metricsConf, fanout); err != nil { - return nil, err - } - - return m, nil -} - -// Gather collects all registered metrics and returns a GatherResponse where the -// metrics are encoded depending on the type. Metrics are either encoded via -// Prometheus or JSON if in-memory. -func (m *Metrics) Gather(format string) (GatherResponse, error) { - switch format { - case FormatPrometheus: - return m.gatherPrometheus() - - case FormatText: - return m.gatherGeneric() - - case FormatDefault: - return m.gatherGeneric() - - default: - return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format) - } -} - -// gatherPrometheus collects Prometheus metrics and returns a GatherResponse. -// If Prometheus metrics are not enabled, it returns an error. -func (m *Metrics) gatherPrometheus() (GatherResponse, error) { - if !m.prometheusEnabled { - return GatherResponse{}, errors.New("prometheus metrics are not enabled") - } - - metricsFamilies, err := prometheus.DefaultGatherer.Gather() - if err != nil { - return GatherResponse{}, fmt.Errorf("failed to gather prometheus metrics: %w", err) - } - - buf := &bytes.Buffer{} - defer buf.Reset() - - e := expfmt.NewEncoder(buf, expfmt.NewFormat(expfmt.TypeTextPlain)) - - for _, mf := range metricsFamilies { - if err := e.Encode(mf); err != nil { - return GatherResponse{}, fmt.Errorf("failed to encode prometheus metrics: %w", err) - } - } - - return GatherResponse{ContentType: ContentTypeText, Metrics: buf.Bytes()}, nil -} - -// gatherGeneric collects generic metrics and returns a GatherResponse. -func (m *Metrics) gatherGeneric() (GatherResponse, error) { - gm, ok := m.sink.(DisplayableSink) - if !ok { - return GatherResponse{}, errors.New("non in-memory metrics sink does not support generic format") - } - - summary, err := gm.DisplayMetrics(nil, nil) - if err != nil { - return GatherResponse{}, fmt.Errorf("failed to gather in-memory metrics: %w", err) - } - - content, err := json.Marshal(summary) - if err != nil { - return GatherResponse{}, fmt.Errorf("failed to encode in-memory metrics: %w", err) - } - - return GatherResponse{ContentType: "application/json", Metrics: content}, nil -} - -// Tracer returns the base tracer for creating spans and logging. -func (m *Metrics) Tracer() log.Tracer { - return m.tracer -} - -// MetricsTracer returns a tracer that only emits metrics for spans. -// Use this when you specifically want to configure a code path to only emit metrics -// and not actual logging spans (useful for benchmarking small operations such as store operations). -func (m *Metrics) MetricsTracer() log.Tracer { - return m.metricsTracer -} - -// Start starts all configured exporters. -// Start should be called after New() in order to ensure that all configured -// exporters are started. -func (m *Metrics) Start(ctx context.Context) error { - for _, f := range m.startFuncs { - if err := f(ctx); err != nil { - return err - } - } - return nil -} - -// Shutdown must be called before the application exits to shutdown any -// exporters and close any open files. -func (m *Metrics) Shutdown(ctx context.Context) error { - n := len(m.shutdownFuncs) - // shutdown in reverse order because we can't close files until after the exporter is stopped - for i := n - 1; i >= 0; i-- { - if err := m.shutdownFuncs[i](ctx); err != nil { - return err - } - } - return nil -} diff --git a/telemetry/metrics_span.go b/telemetry/metrics_span.go deleted file mode 100644 index 407c91708ae1..000000000000 --- a/telemetry/metrics_span.go +++ /dev/null @@ -1,139 +0,0 @@ -package telemetry - -import ( - "context" - "time" - - "github.com/hashicorp/go-metrics" - - "cosmossdk.io/log" -) - -// MetricsTracer is a log.Tracer implementation that emits metrics for span operations. -// It wraps a logger and forwards all log calls to it, while creating MetricsSpan instances -// that emit timing and count metrics. -type MetricsTracer struct { - log.Logger // Logger to forward log calls to - rootPath []string // Base path set at tracer creation, preserved across all spans - rootLabels []metrics.Label // Labels applied to all metrics emitted by this tracer - metrics *metrics.Metrics -} - -// NewMetricsTracer creates a new MetricsTracer with the given configuration. -func NewMetricsTracer(metrics *metrics.Metrics, rootPath []string, rootLabels []metrics.Label, logger log.Logger) *MetricsTracer { - return &MetricsTracer{ - Logger: logger, - rootPath: rootPath, - rootLabels: rootLabels, - metrics: metrics, - } -} - -func (m *MetricsTracer) startSpan(existingPath []string, operation string, _ ...any) log.Span { - path := make([]string, len(existingPath)+1) - copy(path, existingPath) - path[len(path)-1] = operation - return &MetricsSpan{ - MetricsTracer: m, - path: path, - start: time.Now(), - } -} - -func (m *MetricsTracer) StartSpan(operation string, _ ...any) log.Span { - return m.startSpan(m.rootPath, operation) -} - -func (m *MetricsTracer) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - return ctx, m.startSpan(m.rootPath, operation) -} - -func (m *MetricsTracer) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - return ctx, m.startSpan(m.rootPath, operation) -} - -var _ log.Tracer = (*MetricsTracer)(nil) - -// MetricsSpan is a log.Span implementation that emits timing and count metrics -// to go-metrics when the span ends. -// -// Unlike distributed tracing spans, MetricsSpan: -// - Does not support logging (Info/Warn/Error/Debug are no-ops) -// - Ignores span attributes (kvs parameters) -// - Emits aggregated metrics rather than individual trace events -// -// When End() is called, two metrics are emitted: -// - A timer metric with ".time" suffix (e.g., "query.get.time") -// - A counter metric with ".count" suffix (e.g., "query.get.count") -// -// Root path and labels are preserved across all spans created from this tracer, -// ensuring consistent metric namespacing and labeling throughout the span hierarchy. -type MetricsSpan struct { - *MetricsTracer - start time.Time - path []string -} - -// StartSpan creates a child span by appending the operation name to the current path. -// Root path and labels are preserved in the child span. -// The kvs parameters are ignored (metrics don't support dynamic attributes). -func (m *MetricsSpan) StartSpan(operation string, kvs ...any) log.Span { - return m.startSpan(m.path, operation) -} - -// StartSpanContext creates a child span and returns the context unchanged. -// The span is not stored in the context. -func (m *MetricsSpan) StartSpanContext(ctx context.Context, operation string, _ ...any) (context.Context, log.Span) { - return ctx, m.startSpan(m.path, operation) -} - -// StartRootSpan creates a new root span by combining the tracer's root path with the operation. -// Unlike StartSpan, this does not extend the current span's path - it starts fresh from the root path. -// Root labels are preserved in the new span. -// -// Example: -// -// tracer := NewMetricsTracer(metrics, []string{"app"}, nil) -// _, span := tracer.StartRootSpan(ctx, "process") -// defer span.End() -// // Emits: "app.process.time" and "app.process.count" -func (m *MetricsSpan) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - return ctx, m.startSpan(m.rootPath, operation) -} - -// SetAttrs is a no-op - metrics don't support dynamic attributes. -func (m *MetricsSpan) SetAttrs(...any) {} - -// SetErr is a no-op but returns the error unchanged for convenience. -func (m *MetricsSpan) SetErr(err error, _ ...any) error { return err } - -// End emits timing and count metrics with ".time" and ".count" suffixes. -// Root labels are applied to both metrics. -// -// For a span with path ["query", "get"], this emits: -// - Timer: "query.get.time" with duration since start -// - Counter: "query.get.count" incremented by 1 -// -// If root labels were set (e.g., module=staking), they are included in both metrics. -func (m *MetricsSpan) End() { - // Create paths with suffixes to avoid metric type conflicts - timePath := make([]string, len(m.path)+1) - copy(timePath, m.path) - timePath[len(timePath)-1] = "time" - - countPath := make([]string, len(m.path)+1) - copy(countPath, m.path) - countPath[len(countPath)-1] = "count" - - // Apply root labels to metrics - if len(m.rootLabels) > 0 { - m.metrics.MeasureSinceWithLabels(timePath, m.start, m.rootLabels) - m.metrics.IncrCounterWithLabels(countPath, 1, m.rootLabels) - } else { - m.metrics.MeasureSince(timePath, m.start) - m.metrics.IncrCounter(countPath, 1) - } -} - -var _ log.Span = (*MetricsSpan)(nil) -var _ log.Tracer = (*MetricsSpan)(nil) diff --git a/telemetry/metrics_test.go b/telemetry/metrics_test.go deleted file mode 100644 index a0aa4690f81f..000000000000 --- a/telemetry/metrics_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package telemetry - -import ( - "context" - "encoding/json" - "os" - "strings" - "testing" - "time" - - "github.com/hashicorp/go-metrics" - "github.com/stretchr/testify/require" -) - -func TestMetrics_Disabled(t *testing.T) { - m, err := New(Config{Enabled: false}) - require.Nil(t, m) - require.Nil(t, err) -} - -func TestMetrics_InMem(t *testing.T) { - m, err := New(Config{ - MetricsSink: MetricSinkInMem, - Enabled: true, - EnableHostname: false, - ServiceName: "test", - }) - require.NoError(t, err) - require.NotNil(t, m) - - emitMetrics() - - gr, err := m.Gather(FormatText) - require.NoError(t, err) - require.Equal(t, gr.ContentType, "application/json") - - jsonMetrics := make(map[string]any) - require.NoError(t, json.Unmarshal(gr.Metrics, &jsonMetrics)) - - counters := jsonMetrics["Counters"].([]any) - require.Equal(t, counters[0].(map[string]any)["Count"].(float64), 10.0) - require.Equal(t, counters[0].(map[string]any)["Name"].(string), "test.dummy_counter") -} - -func TestMetrics_Prom(t *testing.T) { - m, err := New(Config{ - MetricsSink: MetricSinkInMem, - Enabled: true, - EnableHostname: false, - ServiceName: "test", - PrometheusRetentionTime: 60, - EnableHostnameLabel: false, - }) - require.NoError(t, err) - require.NotNil(t, m) - require.True(t, m.prometheusEnabled) - - emitMetrics() - - gr, err := m.Gather(FormatPrometheus) - require.NoError(t, err) - require.Equal(t, gr.ContentType, ContentTypeText) - - require.True(t, strings.Contains(string(gr.Metrics), "test_dummy_counter 30")) -} - -func TestMetrics_FileSink(t *testing.T) { - tmpfile := t.TempDir() + "/metrics.jsonl" - - m, err := New(Config{ - MetricsSink: MetricSinkFile, - MetricsFile: tmpfile, - Enabled: true, - EnableHostname: false, - ServiceName: "test", - }) - require.NoError(t, err) - require.NotNil(t, m) - - // Emit a few metrics - metrics.IncrCounter([]string{"test_counter"}, 5.0) - metrics.SetGauge([]string{"test_gauge"}, 42.0) - - // Close to flush buffered data - require.NoError(t, m.Shutdown(context.Background())) - - // Verify file was created and contains metrics - data, err := os.ReadFile(tmpfile) - require.NoError(t, err) - require.NotEmpty(t, data) - require.Contains(t, string(data), `"type":"counter"`) - require.Contains(t, string(data), `"type":"gauge"`) - require.Contains(t, string(data), `"key":["test","test_counter"]`) -} - -func emitMetrics() { - ticker := time.NewTicker(time.Second) - timeout := time.After(30 * time.Second) - - for { - select { - case <-ticker.C: - metrics.IncrCounter([]string{"dummy_counter"}, 1.0) - case <-timeout: - return - } - } -} diff --git a/telemetry/options.go b/telemetry/options.go deleted file mode 100644 index b48fe041ecd3..000000000000 --- a/telemetry/options.go +++ /dev/null @@ -1,14 +0,0 @@ -package telemetry - -import "cosmossdk.io/log" - -// Option is a functional option for configuring Metrics. -type Option func(*Metrics) - -// WithLogger sets the default logger to use for tracers. -// If not provided, a nop logger will be used. -func WithLogger(logger log.Logger) Option { - return func(m *Metrics) { - m.logger = logger - } -} diff --git a/telemetry/otel_span.go b/telemetry/otel_span.go deleted file mode 100644 index 3a7db06a24dc..000000000000 --- a/telemetry/otel_span.go +++ /dev/null @@ -1,203 +0,0 @@ -package telemetry - -import ( - "context" - "fmt" - - otelattr "go.opentelemetry.io/otel/attribute" - otelcodes "go.opentelemetry.io/otel/codes" - oteltrace "go.opentelemetry.io/otel/trace" - - "cosmossdk.io/log" -) - -// OtelTracer is a log.Tracer implementation that uses OpenTelemetry for distributed tracing. -// It wraps a logger and forwards all log calls to it, while creating OtelSpan instances -// that emit trace spans. -type OtelTracer struct { - log.Logger - tracer oteltrace.Tracer -} - -func NewOtelTracer(tracer oteltrace.Tracer, logger log.Logger) *OtelTracer { - return &OtelTracer{ - Logger: logger, - tracer: tracer, - } -} - -func (o *OtelTracer) StartSpan(operation string, kvs ...any) log.Span { - ctx, span := o.tracer.Start(context.Background(), operation, oteltrace.WithAttributes(toKVs(kvs)...)) - return &OtelSpan{ - tracer: o.tracer, - ctx: ctx, - span: span, - } -} - -func (o *OtelTracer) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...)) - return ctx, &OtelSpan{ - tracer: o.tracer, - ctx: ctx, - span: span, - } -} - -func (o *OtelTracer) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - ctx, span := o.tracer.Start(ctx, operation, oteltrace.WithAttributes(toKVs(kvs)...), oteltrace.WithNewRoot()) - return ctx, &OtelSpan{ - tracer: o.tracer, - ctx: ctx, - span: span, - } -} - -var _ log.Tracer = (*OtelTracer)(nil) - -type OtelSpan struct { - tracer oteltrace.Tracer - ctx context.Context - span oteltrace.Span - persistentAttrs []otelattr.KeyValue -} - -func (o *OtelSpan) addEvent(level, msg string, keyVals ...any) { - o.span.AddEvent(msg, - oteltrace.WithAttributes(o.persistentAttrs...), - oteltrace.WithAttributes(toKVs(keyVals)...), - oteltrace.WithAttributes(otelattr.String("level", level)), - ) -} - -func (o *OtelSpan) Info(msg string, keyVals ...any) { - o.addEvent("info", msg, keyVals...) -} - -func (o *OtelSpan) Warn(msg string, keyVals ...any) { - o.addEvent("warn", msg, keyVals...) -} - -func (o *OtelSpan) Error(msg string, keyVals ...any) { - o.addEvent("error", msg, keyVals...) -} - -func (o *OtelSpan) Debug(msg string, keyVals ...any) { - o.addEvent("debug", msg, keyVals...) -} - -func (o *OtelSpan) With(keyVals ...any) log.Logger { - attrs := toKVs(keyVals) - persistentAttrs := make([]otelattr.KeyValue, 0, len(o.persistentAttrs)+len(attrs)) - persistentAttrs = append(persistentAttrs, o.persistentAttrs...) - persistentAttrs = append(persistentAttrs, attrs...) - return &OtelSpan{ - tracer: o.tracer, - ctx: o.ctx, - span: o.span, - persistentAttrs: persistentAttrs, - } -} - -func (o *OtelSpan) Impl() any { - return o.span -} - -func (o *OtelSpan) startSpan(ctx context.Context, operation string, kvs []any, opts ...oteltrace.SpanStartOption) *OtelSpan { - if len(o.persistentAttrs) > 0 { - opts = append(opts, oteltrace.WithAttributes(o.persistentAttrs...)) - } - opts = append(opts, oteltrace.WithAttributes(toKVs(kvs)...)) - ctx, span := o.tracer.Start(ctx, operation, opts...) - return &OtelSpan{ - tracer: o.tracer, - ctx: ctx, - span: span, - persistentAttrs: o.persistentAttrs, - } -} - -func (o *OtelSpan) StartSpan(operation string, kvs ...any) log.Span { - return o.startSpan(o.ctx, operation, kvs) -} - -func (o *OtelSpan) StartSpanContext(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - if !oteltrace.SpanContextFromContext(ctx).IsValid() { - // if we don't have a valid span in the context, use the one from the tracer - ctx = o.ctx - } - span := o.startSpan(ctx, operation, kvs) - return span.ctx, span -} - -func (o *OtelSpan) StartRootSpan(ctx context.Context, operation string, kvs ...any) (context.Context, log.Span) { - span := o.startSpan(ctx, operation, kvs, oteltrace.WithNewRoot()) - return span.ctx, span -} - -func (o *OtelSpan) SetAttrs(kvs ...any) { - o.span.SetAttributes(toKVs(kvs)...) -} - -func (o *OtelSpan) SetErr(err error, kvs ...any) error { - if err == nil { - o.span.SetStatus(otelcodes.Ok, "OK") - } else { - o.span.RecordError(err) - o.span.SetStatus(otelcodes.Error, err.Error()) - } - if len(kvs) > 0 { - o.span.SetAttributes(toKVs(kvs)...) - } - return err -} - -func (o *OtelSpan) End() { - o.span.End() -} - -var _ log.Span = (*OtelSpan)(nil) - -func toKVs(kvs []any) []otelattr.KeyValue { - if len(kvs)%2 != 0 { - panic(fmt.Sprintf("kvs must have even length, got %d", len(kvs))) - } - res := make([]otelattr.KeyValue, 0, len(kvs)/2) - for i := 0; i < len(kvs); i += 2 { - key, ok := kvs[i].(string) - if !ok { - panic("key must be string") - } - res = append(res, otelattr.KeyValue{ - Key: otelattr.Key(key), - Value: toValue(kvs[i+1]), - }) - } - return res -} - -func toValue(value any) otelattr.Value { - switch v := value.(type) { - case bool: - return otelattr.BoolValue(v) - case string: - return otelattr.StringValue(v) - case int64: - return otelattr.Int64Value(v) - case int: - return otelattr.IntValue(v) - case float64: - return otelattr.Float64Value(v) - case []string: - return otelattr.StringSliceValue(v) - case []int64: - return otelattr.Int64SliceValue(v) - case []int: - return otelattr.IntSliceValue(v) - case []float64: - return otelattr.Float64SliceValue(v) - default: - return otelattr.StringValue(fmt.Sprintf("%+v", value)) - } - -} diff --git a/telemetry/testing.go b/telemetry/testing.go index f259e978e293..02868632e972 100644 --- a/telemetry/testing.go +++ b/telemetry/testing.go @@ -2,45 +2,16 @@ package telemetry import ( "context" - "encoding/json" - "os" "testing" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/log" ) // TestingInit initializes telemetry for testing. // If ctx is nil, context.Background() is used. // If logger is nil, a new test logger is created. -func TestingInit(t *testing.T, ctx context.Context, logger log.Logger) *Metrics { - t.Helper() - if ctx == nil { - ctx = context.Background() - } - if logger == nil { - logger = log.NewTestLogger(t) - } - - // configure metrics and tracing for testing - telemetryCfg := Config{ - Enabled: true, - ServiceName: "cosmos-sdk-test", - } - telemetryCfgJson, ok := os.LookupEnv("COSMOS_TELEMETRY") - if ok && telemetryCfgJson != "" { - err := json.Unmarshal([]byte(telemetryCfgJson), &telemetryCfg) - require.NoError(t, err, "failed to parse telemetry config", telemetryCfgJson) - } - - t.Logf("Configuring telemetry with: %+v", telemetryCfg) - metrics, err := New(telemetryCfg, WithLogger(logger)) - require.NoError(t, err, "failed to initialize telemetry") - err = metrics.Start(ctx) - require.NoError(t, err, "failed to start telemetry") +func TestingInit(t *testing.T, ctx context.Context) { t.Cleanup(func() { - require.NoError(t, metrics.Shutdown(ctx), "failed to shutdown telemetry") + if err := Shutdown(ctx); err != nil { + t.Fatalf("failed to shutdown telemetry: %v", err) + } }) - return metrics } diff --git a/telemetry/wrapper.go b/telemetry/wrapper.go index 8445ed823830..12d3f43c6875 100644 --- a/telemetry/wrapper.go +++ b/telemetry/wrapper.go @@ -6,6 +6,8 @@ import ( "github.com/hashicorp/go-metrics" ) +var globalLabels []metrics.Label + // Common metric key constants const ( MetricKeyPreBlocker = "pre_blocker" diff --git a/tests/go.mod b/tests/go.mod index fff27db9c449..c7129097442e 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -44,7 +44,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect @@ -131,6 +130,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect @@ -175,7 +175,8 @@ require ( github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect @@ -203,13 +204,23 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect diff --git a/tests/go.sum b/tests/go.sum index b7404e7f61e8..c5aef41227f0 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -53,7 +53,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -446,6 +445,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -717,6 +718,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -724,8 +727,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -848,22 +851,42 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index 7c9b858c29da..e0bb7f0f279d 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -30,7 +30,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect - github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect @@ -91,6 +90,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect @@ -128,7 +128,8 @@ require ( github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect @@ -154,13 +155,24 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.14.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index f3ec451282a3..ea7b7b02107d 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -29,7 +29,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -346,6 +345,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -600,6 +601,8 @@ github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -607,8 +610,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -725,20 +728,42 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= +go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 h1:B/g+qde6Mkzxbry5ZZag0l7QrQBCtVm7lVjaLgmpje8= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0/go.mod h1:mOJK8eMmgW6ocDJn6Bn11CcZ05gi3P8GylBXEkZtbgA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= diff --git a/testutil/network/network.go b/testutil/network/network.go index 138b94bf77de..d4a0dfc9efee 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -25,11 +25,12 @@ import ( "google.golang.org/grpc" "cosmossdk.io/depinject" - "cosmossdk.io/log" sdkmath "cosmossdk.io/math" "cosmossdk.io/math/unsafe" pruningtypes "cosmossdk.io/store/pruning/types" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -377,7 +378,6 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { appCfg.MinGasPrices = cfg.MinGasPrices appCfg.API.Enable = true appCfg.API.Swagger = false - appCfg.Telemetry.Enabled = false ctx := server.NewDefaultContext() cmtCfg := ctx.Config diff --git a/types/abci.go b/types/abci.go index d80439ed8ac0..06ca95d7b620 100644 --- a/types/abci.go +++ b/types/abci.go @@ -6,8 +6,6 @@ import ( abci "github.com/cometbft/cometbft/abci/types" storetypes "cosmossdk.io/store/types" - - "cosmossdk.io/log" ) // ABCIHandlers aggregates all ABCI handlers needed for an application. @@ -101,7 +99,7 @@ func (r ResponsePreBlock) IsConsensusParamsChanged() bool { type RunTx = func(txBytes []byte, tx Tx) (gInfo GasInfo, result *Result, anteEvents []abci.Event, err error) // DeliverTxFunc is the function called for each transaction in order to produce a single ExecTxResult -type DeliverTxFunc func(tx []byte, ms storetypes.MultiStore, txIndex int, incarnationCache map[string]any, tracer log.Tracer) *abci.ExecTxResult +type DeliverTxFunc func(tx []byte, ms storetypes.MultiStore, txIndex int, incarnationCache map[string]any) *abci.ExecTxResult // TxRunner defines an interface for types which can be used to execute the DeliverTxFunc. // It should return an array of *abci.ExecTxResult corresponding to the result of executing each transaction diff --git a/types/context.go b/types/context.go index 4e762cc15943..7247783be320 100644 --- a/types/context.go +++ b/types/context.go @@ -6,12 +6,14 @@ import ( abci "github.com/cometbft/cometbft/abci/types" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "go.opentelemetry.io/otel/trace" "cosmossdk.io/core/comet" "cosmossdk.io/core/header" - "cosmossdk.io/log" "cosmossdk.io/store/gaskv" storetypes "cosmossdk.io/store/types" + + "cosmossdk.io/log" ) // ExecMode defines the execution mode which can be set on a Context. @@ -439,6 +441,11 @@ func (c Context) WithIncarnationCache(cache map[string]any) Context { return c } +func (c Context) StartSpan(tracer trace.Tracer, spanName string, opts ...trace.SpanStartOption) (Context, trace.Span) { + goCtx, span := tracer.Start(c.baseCtx, spanName, opts...) + return c.WithContext(goCtx), span +} + var ( _ context.Context = Context{} _ storetypes.Context = Context{} From 50775674c9a7b863401cb6daedc3794a436a9e69 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 13:43:39 -0400 Subject: [PATCH 51/87] fixes --- simapp/sim_test.go | 4 ---- telemetry/config.go | 6 ++++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/simapp/sim_test.go b/simapp/sim_test.go index ae9607fc386f..4336d2aaf3b5 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -18,7 +18,6 @@ import ( dbm "github.com/cosmos/cosmos-db" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel" "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/telemetry" @@ -162,9 +161,6 @@ func IsEmptyValidatorSetErr(err error) bool { func TestAppStateDeterminism(t *testing.T) { telemetry.TestingInit(t, context.Background()) - tracer := otel.Tracer("test") - _, span := tracer.Start(context.Background(), "TestAppStateDeterminism") - defer span.End() const numTimesToRunPerSeed = 1 var seeds []int64 if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue { diff --git a/telemetry/config.go b/telemetry/config.go index 440b7002227d..81a5a7ca58c3 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -7,6 +7,8 @@ import ( "os" "go.opentelemetry.io/contrib/otelconf/v0.3.0" + "go.opentelemetry.io/otel" + logglobal "go.opentelemetry.io/otel/log/global" ) var sdk otelconf.SDK @@ -43,6 +45,10 @@ func init() { if err != nil { panic(fmt.Sprintf("failed to initialize telemetry: %v", err)) } + + otel.SetTracerProvider(sdk.TracerProvider()) + otel.SetMeterProvider(sdk.MeterProvider()) + logglobal.SetLoggerProvider(sdk.LoggerProvider()) } func Shutdown(ctx context.Context) error { From 31536b6cdb61e71a9bc59e0af82d50f3144136b7 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 14:13:39 -0400 Subject: [PATCH 52/87] add basic metrics --- baseapp/abci.go | 4 ++++ baseapp/baseapp.go | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index e1aabdc64216..ae5f6f888818 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -1033,6 +1033,10 @@ func (app *BaseApp) Commit() (*abci.ResponseCommit, error) { // The SnapshotIfApplicable method will create the snapshot by starting the goroutine app.snapshotManager.SnapshotIfApplicable(header.Height) + blockCnt.Add(ctx, 1) + blockTime.Record(ctx, time.Since(app.blockStartTime).Seconds()) + app.blockStartTime = time.Now() + return resp, nil } diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index fb502333e6f6..1b1df303cdef 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -7,6 +7,7 @@ import ( "slices" "strconv" "sync" + "time" "github.com/cockroachdb/errors" abci "github.com/cometbft/cometbft/abci/types" @@ -16,6 +17,7 @@ import ( "github.com/cosmos/gogoproto/proto" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" protov2 "google.golang.org/protobuf/proto" @@ -64,9 +66,40 @@ const ( var _ servertypes.ABCI = (*BaseApp)(nil) var ( - tracer trace.Tracer = otel.Tracer("baseapp") + tracer = otel.Tracer("baseapp") + meter = otel.Meter("baseapp") + blockCnt metric.Int64Counter + txCnt metric.Int64Counter + blockTime metric.Float64Histogram + txTime metric.Int64Histogram ) +func init() { + var err error + blockCnt, err = meter.Int64Counter("block.count") + if err != nil { + panic(err) + } + txCnt, err = meter.Int64Counter("tx.count") + if err != nil { + panic(err) + } + blockTime, err = meter.Float64Histogram("block.time", + metric.WithUnit("s"), + metric.WithDescription("Block time in seconds"), + ) + if err != nil { + panic(err) + } + txTime, err = meter.Int64Histogram("tx.time", + metric.WithUnit("us"), + metric.WithDescription("Transaction time in microseconds"), + ) + if err != nil { + panic(err) + } +} + // BaseApp reflects the ABCI application implementation. type BaseApp struct { // initialized on creation @@ -172,6 +205,8 @@ type BaseApp struct { // Optional alternative tx runner, used for block-stm parallel transaction execution. If nil, default txRunner is used. txRunner sdk.TxRunner + + blockStartTime time.Time } // NewBaseApp returns a reference to an initialized BaseApp. It accepts a @@ -192,6 +227,7 @@ func NewBaseApp( fauxMerkleMode: false, sigverifyTx: true, gasConfig: config.GasConfig{QueryGasLimit: math.MaxUint64}, + blockStartTime: time.Now(), } // initialize tracer @@ -791,6 +827,7 @@ func (app *BaseApp) endBlock() (sdk.EndBlock, error) { // both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice // passing the decoded tx to runTX is optional, it will be decoded if the tx is nil func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex int, txMultiStore storetypes.MultiStore, incarnationCache map[string]any) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { + startTime := time.Now() ctx := app.getContextForTx(mode, txBytes, txIndex) ctx, span := ctx.StartSpan(tracer, "runTx") defer span.End() @@ -974,6 +1011,9 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex consumeBlockGas() msCache.Write() + + txCnt.Add(ctx, 1) + txTime.Record(ctx, time.Since(startTime).Microseconds()) } if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) { From c922688e2d08cb9458d6db457280c71af696d945 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 14:30:38 -0400 Subject: [PATCH 53/87] add telemetry shutdown hook --- server/start.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/start.go b/server/start.go index 4bb4c29b1e0d..36e874ea2ab4 100644 --- a/server/start.go +++ b/server/start.go @@ -607,6 +607,12 @@ func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions cleanupFn = func() { traceCleanupFn() + + shutdownCtx, _ := context.WithTimeout(context.Background(), 5*time.Second) + if err := telemetry.Shutdown(shutdownCtx); err != nil { + svrCtx.Logger.Error("failed to shutdown telemetry", "error", err) + } + if localErr := app.Close(); localErr != nil { svrCtx.Logger.Error(localErr.Error()) } From ed891ccc32a352d4f8e7d18adf44a546ef954c2d Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 14:40:12 -0400 Subject: [PATCH 54/87] docs, cleanup --- go.mod | 6 ++---- go.sum | 2 ++ simapp/go.mod | 2 -- simapp/go.sum | 2 ++ telemetry/doc.go | 18 +++++++++++++++++- telemetry/testing.go | 7 +++++-- types/context.go | 3 +++ 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9f4d8319c8ae..720a8f833b66 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,8 @@ require ( github.com/tidwall/btree v1.8.1 go.opentelemetry.io/contrib/otelconf v0.18.0 go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/log v0.14.0 + go.opentelemetry.io/otel/metric v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/mock v0.6.0 golang.org/x/crypto v0.43.0 @@ -225,8 +227,6 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect - go.opentelemetry.io/otel/log v0.14.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect @@ -254,8 +254,6 @@ require ( // Here are the short-lived replace from the Cosmos SDK // Replace here are pending PRs, or version to be tagged -replace cosmossdk.io/log => ./log - // Below are the long-lived replace of the Cosmos SDK replace ( // use cosmos fork of keyring diff --git a/go.sum b/go.sum index a090e17f9037..b4025a518e6f 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= diff --git a/simapp/go.mod b/simapp/go.mod index f3b7f18f7733..1100e0357d99 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -251,8 +251,6 @@ require ( sigs.k8s.io/yaml v1.6.0 // indirect ) -replace cosmossdk.io/log => ../log - // Below are the long-lived replace of the SimApp replace ( // use cosmos fork of keyring diff --git a/simapp/go.sum b/simapp/go.sum index 016a7cbf300d..d26be9d2bbf0 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -34,6 +34,8 @@ cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= diff --git a/telemetry/doc.go b/telemetry/doc.go index 7019bd9a9563..a56752fd3cc8 100644 --- a/telemetry/doc.go +++ b/telemetry/doc.go @@ -1,2 +1,18 @@ -// Package telemetry initialize OpenTelemetry and provides metrics wrapper functions. +// Package telemetry initializes OpenTelemetry and provides legacy metrics wrapper functions. +// End users only need to set the COSMOS_TELEMETRY environment variable to the path of +// an OpenTelemetry declarative configuration file: https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/ +// +// Developers need to do two things: +// 1. Import this package before declaring any otel Tracer, Meter or Logger instances. +// 2. Make sure Shutdown() is called when the application is shutting down. +// Tests can use the TestingInit function at startup to accomplish this. +// +// If these steps are followed, developers can follow the official golang otel conventions +// of declaring package-level tracer and meter instances using otel.Tracer() and otel.Meter(). +// NOTE: it is important to thread context.Context properly for spans, metrics and logs to be +// correlated correctly. +// When using the SDK's context type, spans must be started with Context.StartSpan to +// get an SDK context which has the span set correctly. +// For logging, go.opentelemetry.io/contrib/bridges/otelslog provides a way to do this with the standard +// library slog package. package telemetry diff --git a/telemetry/testing.go b/telemetry/testing.go index 02868632e972..df9ac2085a18 100644 --- a/telemetry/testing.go +++ b/telemetry/testing.go @@ -5,10 +5,13 @@ import ( "testing" ) -// TestingInit initializes telemetry for testing. +// TestingInit initializes telemetry for testing so that it is automatically +// shutdown after the test completes. // If ctx is nil, context.Background() is used. -// If logger is nil, a new test logger is created. func TestingInit(t *testing.T, ctx context.Context) { + if ctx == nil { + ctx = context.Background() + } t.Cleanup(func() { if err := Shutdown(ctx); err != nil { t.Fatalf("failed to shutdown telemetry: %v", err) diff --git a/types/context.go b/types/context.go index 7247783be320..34f4a85cfd2f 100644 --- a/types/context.go +++ b/types/context.go @@ -441,6 +441,9 @@ func (c Context) WithIncarnationCache(cache map[string]any) Context { return c } +// StartSpan starts an otel span and returns a new context with the span attached. +// Use this instead of calling tracer.Start directly to have the span correctly +// attached to this context type. func (c Context) StartSpan(tracer trace.Tracer, spanName string, opts ...trace.SpanStartOption) (Context, trace.Span) { goCtx, span := tracer.Start(c.baseCtx, spanName, opts...) return c.WithContext(goCtx), span From f685bd45c908b12feb962263554501407ff819f2 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 14:45:15 -0400 Subject: [PATCH 55/87] WIP on removing go-metrics --- telemetry/wrapper.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/telemetry/wrapper.go b/telemetry/wrapper.go index 12d3f43c6875..1d28e8af4d73 100644 --- a/telemetry/wrapper.go +++ b/telemetry/wrapper.go @@ -1,9 +1,12 @@ package telemetry import ( + "sync" "time" "github.com/hashicorp/go-metrics" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" ) var globalLabels []metrics.Label @@ -16,6 +19,17 @@ const ( MetricLabelNameModule = "module" ) +var meter = otel.Meter("cosmos-sdk") +var mtx sync.RWMutex +var counters map[string]metric.Float64Counter +var gauges map[string]metric.Float64Gauge +var histograms map[string]metric.Float64Histogram + +type Label struct { + Name string + Value string +} + // NewLabel creates a new instance of Label with name and value func NewLabel(name, value string) metrics.Label { return metrics.Label{Name: name, Value: value} From 699f5d31779bbf5b3316ca9fd39d133acad1e6d1 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 15:48:04 -0400 Subject: [PATCH 56/87] setup sim test flag --- simapp/sim_test.go | 6 ++++-- types/simulation/config.go | 1 + x/simulation/client/cli/flags.go | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 4336d2aaf3b5..9be125236cbf 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/telemetry" "cosmossdk.io/store" @@ -161,9 +162,10 @@ func IsEmptyValidatorSetErr(err error) bool { func TestAppStateDeterminism(t *testing.T) { telemetry.TestingInit(t, context.Background()) - const numTimesToRunPerSeed = 1 + cfg := simcli.NewConfigFromFlags() + numTimesToRunPerSeed := cfg.NumRuns var seeds []int64 - if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue { + if s := cfg.Seed; s != simcli.DefaultSeedValue { // We will be overriding the random seed and just run a single simulation on the provided seed value for j := 0; j < numTimesToRunPerSeed; j++ { // multiple rounds seeds = append(seeds, s) diff --git a/types/simulation/config.go b/types/simulation/config.go index 1e385fdfa1d6..b903ed3bc1f7 100644 --- a/types/simulation/config.go +++ b/types/simulation/config.go @@ -16,6 +16,7 @@ type Config struct { InitialBlockHeight int // initial block to start the simulation GenesisTime int64 // genesis time to start the simulation NumBlocks int // number of new blocks to simulate from the initial block height + NumRuns int // number of times to run the simulation for simulations that have multiple runs BlockSize int // operations per block ChainID string // chain-id used on the simulation diff --git a/x/simulation/client/cli/flags.go b/x/simulation/client/cli/flags.go index 8479b83f5e1c..c8552fec91bc 100644 --- a/x/simulation/client/cli/flags.go +++ b/x/simulation/client/cli/flags.go @@ -20,6 +20,7 @@ var ( FlagSeedValue int64 FlagInitialBlockHeightValue int FlagNumBlocksValue int + FlagNumRunsValue int FlagBlockSizeValue int FlagLeanValue bool FlagCommitValue bool @@ -51,6 +52,7 @@ func GetSimulatorFlags() { flag.StringVar(&FlagExportStatsPathValue, "ExportStatsPath", "", "custom file path to save the exported simulation statistics JSON") flag.Int64Var(&FlagSeedValue, "Seed", DefaultSeedValue, "simulation random seed") flag.IntVar(&FlagInitialBlockHeightValue, "InitialBlockHeight", 1, "initial block to start the simulation") + flag.IntVar(&FlagNumRunsValue, "NumRuns", 3, "number of runs to run for simulations that do multiple runs at once") flag.IntVar(&FlagNumBlocksValue, "NumBlocks", 500, "number of new blocks to simulate from the initial block height") flag.IntVar(&FlagBlockSizeValue, "BlockSize", 200, "operations per block") flag.BoolVar(&FlagLeanValue, "Lean", false, "lean simulation log output") @@ -81,6 +83,7 @@ func NewConfigFromFlags() simulation.Config { Seed: FlagSeedValue, InitialBlockHeight: FlagInitialBlockHeightValue, GenesisTime: FlagGenesisTimeValue, + NumRuns: FlagNumRunsValue, NumBlocks: FlagNumBlocksValue, BlockSize: FlagBlockSizeValue, Lean: FlagLeanValue, From 5df24604d0e409022e5691b11e742ee7d3ba5cb6 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 16:32:34 -0400 Subject: [PATCH 57/87] integrate slog logging --- client/v2/go.mod | 1 + client/v2/go.sum | 2 ++ go.mod | 1 + go.sum | 2 ++ simapp/go.mod | 1 + simapp/go.sum | 2 ++ systemtests/go.mod | 1 + systemtests/go.sum | 2 ++ telemetry/config.go | 7 +++++++ tests/go.mod | 1 + tests/go.sum | 2 ++ tests/systemtests/go.mod | 1 + tests/systemtests/go.sum | 2 ++ 13 files changed, 25 insertions(+) diff --git a/client/v2/go.mod b/client/v2/go.mod index 1f1aeb58a265..6e8810501d61 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -149,6 +149,7 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index a26eb6a85ec5..670ac1e03247 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -721,6 +721,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= diff --git a/go.mod b/go.mod index 720a8f833b66..8b41a692b3ec 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( github.com/tendermint/go-amino v0.16.0 github.com/test-go/testify v1.1.4 github.com/tidwall/btree v1.8.1 + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 go.opentelemetry.io/contrib/otelconf v0.18.0 go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel/log v0.14.0 diff --git a/go.sum b/go.sum index b4025a518e6f..6cf83a395b8b 100644 --- a/go.sum +++ b/go.sum @@ -838,6 +838,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= diff --git a/simapp/go.mod b/simapp/go.mod index 1100e0357d99..6168b1eafde7 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -201,6 +201,7 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index d26be9d2bbf0..bca05daab254 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -846,6 +846,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= diff --git a/systemtests/go.mod b/systemtests/go.mod index 9affc6f367c3..ac8cc478f354 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -149,6 +149,7 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index 7e5dfcf655aa..aa4352f4a16e 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -730,6 +730,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= diff --git a/telemetry/config.go b/telemetry/config.go index 81a5a7ca58c3..461fc9d1f275 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "os" + "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/otel" logglobal "go.opentelemetry.io/otel/log/global" @@ -46,9 +48,14 @@ func init() { panic(fmt.Sprintf("failed to initialize telemetry: %v", err)) } + // setup otel global providers otel.SetTracerProvider(sdk.TracerProvider()) otel.SetMeterProvider(sdk.MeterProvider()) logglobal.SetLoggerProvider(sdk.LoggerProvider()) + // setup slog default provider so that any logs emitted the default slog will be traced + slog.SetDefault(otelslog.NewLogger("", otelslog.WithSource(true))) + // emit an initialized message which verifies basic telemetry is working + slog.Info("Telemetry initialized") } func Shutdown(ctx context.Context) error { diff --git a/tests/go.mod b/tests/go.mod index c7129097442e..6dc60d9ba6d8 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -201,6 +201,7 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index c5aef41227f0..55cb1ce10c9a 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -845,6 +845,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index e0bb7f0f279d..fcff5d1ed271 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -155,6 +155,7 @@ require ( github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index ea7b7b02107d..e393aa40834a 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -728,6 +728,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= +go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= From 1c84edb805fb11210df5d3aeaff1f295db03017c Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 31 Oct 2025 16:40:11 -0400 Subject: [PATCH 58/87] update to use official env var --- telemetry/config.go | 2 +- telemetry/doc.go | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/telemetry/config.go b/telemetry/config.go index 461fc9d1f275..4689ad2fdfce 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -22,7 +22,7 @@ func init() { var opts []otelconf.ConfigurationOption - confFilename := os.Getenv("COSMOS_TELEMETRY") + confFilename := os.Getenv("OTEL_EXPERIMENTAL_CONFIG_FILE") if confFilename != "" { bz, err := os.ReadFile(confFilename) if err != nil { diff --git a/telemetry/doc.go b/telemetry/doc.go index a56752fd3cc8..c3c37264051f 100644 --- a/telemetry/doc.go +++ b/telemetry/doc.go @@ -1,6 +1,10 @@ // Package telemetry initializes OpenTelemetry and provides legacy metrics wrapper functions. -// End users only need to set the COSMOS_TELEMETRY environment variable to the path of -// an OpenTelemetry declarative configuration file: https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/ +// While manual OpenTelemetry initialization is still supported, this package provides a single +// point of initialization such that end users can just use the official +// OpenTelemetry declarative configuration spec: https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/ +// End users only need to set the OTEL_EXPERIMENTAL_CONFIG_FILE environment variable to the path of +// an OpenTelemetry configuration file and that's it. +// All the documentation necessary is provided in the OpenTelemetry documentation. // // Developers need to do two things: // 1. Import this package before declaring any otel Tracer, Meter or Logger instances. From 46e4bcb98dbb451167e3ebd1795a9d2b0ce671f3 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 13:00:07 -0500 Subject: [PATCH 59/87] add README.md --- telemetry/README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++ telemetry/doc.go | 20 -------------- 2 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 telemetry/README.md diff --git a/telemetry/README.md b/telemetry/README.md new file mode 100644 index 000000000000..6dcf0ac9caa5 --- /dev/null +++ b/telemetry/README.md @@ -0,0 +1,65 @@ +## Quick Start For Local Telemetry + +To quickly setup a local telemetry environment where OpenTelemetry data is sent to a local instance of Grafana LGTM: +1. start the [Grafana LGTM docker image](https://hub.docker.com/r/grafana/otel-lgtm): + ```shell + docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm + ``` +2. create a basic OpenTelemetry configuration file which will send data to the local instance of Grafana LGTM: + ```yaml + resource: + attributes: + - name: service.name + value: my_app_name + tracer_provider: + processors: + - simple: # NOTE: you should use batch in production! + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 + meter_provider: + readers: + - periodic: + interval: 100 # 100 milliseconds, use something longer in production! + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 + logger_provider: + processors: + - simple: # NOTE: you should use batch in production! + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 + ``` +3. set the `OTEL_EXPERIMENTAL_CONFIG_FILE` environment variable to the path of the configuration file: +`export OTEL_EXPERIMENTAL_CONFIG_FILE=path/to/config.yaml` +4. start your application or tests +5. view the data in Grafana LGTM at http://localhost:3000/. The Drilldown views are suggested for getting started. + +## OpenTelemetry Initialization + +While manual OpenTelemetry initialization is still supported, this package provides a single +point of initialization such that end users can just use the official +OpenTelemetry declarative configuration spec: https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/ +End users only need to set the `OTEL_EXPERIMENTAL_CONFIG_FILE` environment variable to the path of +an OpenTelemetry configuration file and that's it. +All the documentation necessary is provided in the OpenTelemetry documentation. + +## Developer Usage + +Developers need to do two things to use this package properly: + 1. Import this package before declaring any otel Tracer, Meter or Logger instances. + 2. Make sure Shutdown() is called when the application is shutting down. + Tests can use the TestingInit function at startup to accomplish this. + +If these steps are followed, developers can follow the official golang otel conventions +of declaring package-level tracer and meter instances using otel.Tracer() and otel.Meter(). +NOTE: it is important to thread context.Context properly for spans, metrics and logs to be +correlated correctly. +When using the SDK's context type, spans must be started with Context.StartSpan to +get an SDK context which has the span set correctly. +For logging, go.opentelemetry.io/contrib/bridges/otelslog provides a way to do this with the standard +library slog package. diff --git a/telemetry/doc.go b/telemetry/doc.go index c3c37264051f..606ace81fdd8 100644 --- a/telemetry/doc.go +++ b/telemetry/doc.go @@ -1,22 +1,2 @@ // Package telemetry initializes OpenTelemetry and provides legacy metrics wrapper functions. -// While manual OpenTelemetry initialization is still supported, this package provides a single -// point of initialization such that end users can just use the official -// OpenTelemetry declarative configuration spec: https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/ -// End users only need to set the OTEL_EXPERIMENTAL_CONFIG_FILE environment variable to the path of -// an OpenTelemetry configuration file and that's it. -// All the documentation necessary is provided in the OpenTelemetry documentation. -// -// Developers need to do two things: -// 1. Import this package before declaring any otel Tracer, Meter or Logger instances. -// 2. Make sure Shutdown() is called when the application is shutting down. -// Tests can use the TestingInit function at startup to accomplish this. -// -// If these steps are followed, developers can follow the official golang otel conventions -// of declaring package-level tracer and meter instances using otel.Tracer() and otel.Meter(). -// NOTE: it is important to thread context.Context properly for spans, metrics and logs to be -// correlated correctly. -// When using the SDK's context type, spans must be started with Context.StartSpan to -// get an SDK context which has the span set correctly. -// For logging, go.opentelemetry.io/contrib/bridges/otelslog provides a way to do this with the standard -// library slog package. package telemetry From f0c3955550b030a879d88e08fe647037cb8db9c0 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 13:01:50 -0500 Subject: [PATCH 60/87] delete spaces --- telemetry/README.md | 62 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/telemetry/README.md b/telemetry/README.md index 6dcf0ac9caa5..9df20c3ec5fc 100644 --- a/telemetry/README.md +++ b/telemetry/README.md @@ -2,38 +2,38 @@ To quickly setup a local telemetry environment where OpenTelemetry data is sent to a local instance of Grafana LGTM: 1. start the [Grafana LGTM docker image](https://hub.docker.com/r/grafana/otel-lgtm): - ```shell - docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm - ``` +```shell +docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm +``` 2. create a basic OpenTelemetry configuration file which will send data to the local instance of Grafana LGTM: - ```yaml - resource: - attributes: - - name: service.name - value: my_app_name - tracer_provider: - processors: - - simple: # NOTE: you should use batch in production! - exporter: - otlp: - protocol: grpc - endpoint: http://localhost:4317 - meter_provider: - readers: - - periodic: - interval: 100 # 100 milliseconds, use something longer in production! - exporter: - otlp: - protocol: grpc - endpoint: http://localhost:4317 - logger_provider: - processors: - - simple: # NOTE: you should use batch in production! - exporter: - otlp: - protocol: grpc - endpoint: http://localhost:4317 - ``` +```yaml +resource: + attributes: + - name: service.name + value: my_app_name +tracer_provider: + processors: + - simple: # NOTE: you should use batch in production! + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 +meter_provider: + readers: + - periodic: + interval: 100 # 100 milliseconds, use something longer in production! + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 +logger_provider: + processors: + - simple: # NOTE: you should use batch in production! + exporter: + otlp: + protocol: grpc + endpoint: http://localhost:4317 +``` 3. set the `OTEL_EXPERIMENTAL_CONFIG_FILE` environment variable to the path of the configuration file: `export OTEL_EXPERIMENTAL_CONFIG_FILE=path/to/config.yaml` 4. start your application or tests From 7dfb754bf366e1e7aedc4414b081004075686233 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 17:55:20 -0500 Subject: [PATCH 61/87] setup TestingMain --- simapp/sim_test.go | 9 +++++---- telemetry/testing.go | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/simapp/sim_test.go b/simapp/sim_test.go index 9be125236cbf..4f2bf6ad95f0 100644 --- a/simapp/sim_test.go +++ b/simapp/sim_test.go @@ -3,7 +3,6 @@ package simapp import ( - "context" "encoding/binary" "encoding/json" "flag" @@ -21,13 +20,12 @@ import ( "cosmossdk.io/log" - "github.com/cosmos/cosmos-sdk/telemetry" - "cosmossdk.io/store" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp" servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/telemetry" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sims "github.com/cosmos/cosmos-sdk/testutil/simsx" sdk "github.com/cosmos/cosmos-sdk/types" @@ -48,6 +46,10 @@ func init() { flag.BoolVar(&FlagEnableStreamingValue, "EnableStreaming", false, "Enable streaming service") } +func TestMain(m *testing.M) { + telemetry.TestingMain(m, nil) +} + // interBlockCacheOpt returns a BaseApp option function that sets the persistent // inter-block write-through cache. func interBlockCacheOpt() func(*baseapp.BaseApp) { @@ -161,7 +163,6 @@ func IsEmptyValidatorSetErr(err error) bool { } func TestAppStateDeterminism(t *testing.T) { - telemetry.TestingInit(t, context.Background()) cfg := simcli.NewConfigFromFlags() numTimesToRunPerSeed := cfg.NumRuns var seeds []int64 diff --git a/telemetry/testing.go b/telemetry/testing.go index df9ac2085a18..623a7e93e7cf 100644 --- a/telemetry/testing.go +++ b/telemetry/testing.go @@ -2,19 +2,26 @@ package telemetry import ( "context" + "fmt" + "os" "testing" ) -// TestingInit initializes telemetry for testing so that it is automatically -// shutdown after the test completes. +// TestingMain should be used in tests where you want to run telemetry and need clean shutdown +// behavior at the end of the test, for instance to collect benchmark metrics. // If ctx is nil, context.Background() is used. -func TestingInit(t *testing.T, ctx context.Context) { +// Example: +// +// func TestMain(m *testing.M) { +// telemetry.TestingMain(m, nil) +// } +func TestingMain(m *testing.M, ctx context.Context) { + code := m.Run() if ctx == nil { ctx = context.Background() } - t.Cleanup(func() { - if err := Shutdown(ctx); err != nil { - t.Fatalf("failed to shutdown telemetry: %v", err) - } - }) + if err := Shutdown(ctx); err != nil { + fmt.Printf("failed to shutdown telemetry after test completion: %v\n", err) + } + os.Exit(code) } From 1ce344b3aa223f6484b4f528bebf27720a952a5e Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 17:55:57 -0500 Subject: [PATCH 62/87] update suggested config in README.md --- telemetry/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telemetry/README.md b/telemetry/README.md index 9df20c3ec5fc..bbaabbe34c01 100644 --- a/telemetry/README.md +++ b/telemetry/README.md @@ -13,7 +13,7 @@ resource: value: my_app_name tracer_provider: processors: - - simple: # NOTE: you should use batch in production! + - batch: # NOTE: you should use batch in production! exporter: otlp: protocol: grpc @@ -21,14 +21,14 @@ tracer_provider: meter_provider: readers: - periodic: - interval: 100 # 100 milliseconds, use something longer in production! + interval: 1000 # 1 second, maybe use something longer in production exporter: otlp: protocol: grpc endpoint: http://localhost:4317 logger_provider: processors: - - simple: # NOTE: you should use batch in production! + - batch: exporter: otlp: protocol: grpc From edbae924e5beb19d3192502cc0cd9f4fd57e6362 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 18:31:52 -0500 Subject: [PATCH 63/87] add otel custom config options --- go.mod | 24 +++++-- go.sum | 24 +++++++ simapp/go.mod | 10 +++ simapp/go.sum | 24 +++++++ systemtests/go.mod | 10 +++ systemtests/go.sum | 24 +++++++ telemetry/config.go | 141 +++++++++++++++++++++++++++++++++++++-- tests/go.mod | 10 +++ tests/go.sum | 24 +++++++ tests/systemtests/go.mod | 10 +++ tests/systemtests/go.sum | 24 +++++++ 11 files changed, 313 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 8b41a692b3ec..ec583f892f44 100644 --- a/go.mod +++ b/go.mod @@ -56,12 +56,21 @@ require ( github.com/test-go/testify v1.1.4 github.com/tidwall/btree v1.8.1 go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 go.opentelemetry.io/contrib/otelconf v0.18.0 go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 go.opentelemetry.io/otel/log v0.14.0 go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/sdk/log v0.14.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/mock v0.6.0 + go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.43.0 golang.org/x/sync v0.17.0 google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 @@ -132,6 +141,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -145,6 +155,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -177,6 +188,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -190,6 +202,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect @@ -200,14 +213,18 @@ require ( github.com/rs/cors v1.11.1 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.16 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/errs v1.4.0 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -225,17 +242,10 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.22.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.46.0 // indirect diff --git a/go.sum b/go.sum index 6cf83a395b8b..92042504ea99 100644 --- a/go.sum +++ b/go.sum @@ -275,6 +275,8 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -339,6 +341,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= @@ -564,6 +569,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -683,6 +690,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -744,6 +753,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -808,6 +819,10 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -821,6 +836,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= @@ -844,8 +861,12 @@ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8F go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -1016,6 +1037,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1033,6 +1055,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1052,6 +1075,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/simapp/go.mod b/simapp/go.mod index 6168b1eafde7..e87d8b3b03c6 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -101,6 +101,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -114,6 +115,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -159,6 +161,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -174,6 +177,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect @@ -185,6 +189,7 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect @@ -193,8 +198,11 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.8.1 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/errs v1.4.0 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -204,7 +212,9 @@ require ( go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index bca05daab254..6521c07eb831 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -286,6 +286,8 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -350,6 +352,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= @@ -574,6 +579,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -693,6 +700,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -754,6 +763,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -816,6 +827,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -829,6 +844,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= @@ -852,8 +869,12 @@ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8F go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -1024,6 +1045,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1041,6 +1063,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1060,6 +1083,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/systemtests/go.mod b/systemtests/go.mod index ac8cc478f354..7230935f0337 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -60,6 +60,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -70,6 +71,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -108,6 +110,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/highwayhash v1.0.3 // indirect @@ -119,6 +122,7 @@ require ( github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect @@ -130,6 +134,7 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -143,13 +148,18 @@ require ( github.com/tidwall/btree v1.8.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index aa4352f4a16e..a4e3d7c18a49 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -200,6 +200,8 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -254,6 +256,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -461,6 +466,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -577,6 +584,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -638,6 +647,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -704,6 +715,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -715,6 +730,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -732,6 +749,10 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -887,6 +908,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -903,6 +925,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -921,6 +944,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/telemetry/config.go b/telemetry/config.go index 4689ad2fdfce..0bd329fb9828 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -8,16 +8,33 @@ import ( "os" "go.opentelemetry.io/contrib/bridges/otelslog" + "go.opentelemetry.io/contrib/instrumentation/host" + "go.opentelemetry.io/contrib/instrumentation/runtime" "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" logglobal "go.opentelemetry.io/otel/log/global" + logsdk "go.opentelemetry.io/otel/sdk/log" + metricsdk "go.opentelemetry.io/otel/sdk/metric" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.yaml.in/yaml/v3" ) var sdk otelconf.SDK +var shutdownFuncs []func(context.Context) error var isTelemetryEnabled = true func init() { + err := doInit() + if err != nil { + panic(err) + } +} + +func doInit() error { var err error var opts []otelconf.ConfigurationOption @@ -26,26 +43,118 @@ func init() { if confFilename != "" { bz, err := os.ReadFile(confFilename) if err != nil { - panic(fmt.Sprintf("failed to read telemetry config file: %v", err)) + return fmt.Errorf("failed to read telemetry config file: %w", err) } cfg, err := otelconf.ParseYAML(bz) if err != nil { - panic(fmt.Sprintf("failed to parse telemetry config file: %v", err)) + return fmt.Errorf("failed to parse telemetry config file: %w", err) } cfgJson, err := json.Marshal(cfg) if err != nil { - panic(fmt.Sprintf("failed to marshal telemetry config file: %v", err)) + return fmt.Errorf("failed to marshal telemetry config file: %w", err) } fmt.Printf("\nInitializing telemetry with config:\n%s\n\n", cfgJson) opts = append(opts, otelconf.WithOpenTelemetryConfiguration(*cfg)) + + // parse cosmos extra config + var extraCfg extraConfig + err = yaml.Unmarshal(bz, &extraCfg) + if err == nil { + if extraCfg.CosmosExtra != nil { + extra := *extraCfg.CosmosExtra + if extra.TraceFile != "" { + fmt.Printf("Initializing trace file: %s\n", extra.TraceFile) + traceFile, err := os.OpenFile(extra.TraceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("failed to open trace file: %w", err) + } + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + if err := traceFile.Close(); err != nil { + return fmt.Errorf("failed to close trace file: %w", err) + } + return nil + }) + exporter, err := stdouttrace.New( + stdouttrace.WithWriter(traceFile), + stdouttrace.WithPrettyPrint(), + ) + if err != nil { + return fmt.Errorf("failed to create stdout trace exporter: %w", err) + } + opts = append(opts, otelconf.WithTracerProviderOptions( + tracesdk.WithBatcher(exporter), + )) + } + if extra.MetricsFile != "" { + fmt.Printf("Initializing metrics file: %s\n", extra.MetricsFile) + metricsFile, err := os.OpenFile(extra.MetricsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("failed to open metrics file: %w", err) + } + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + if err := metricsFile.Close(); err != nil { + return fmt.Errorf("failed to close metrics file: %w", err) + } + return nil + }) + exporter, err := stdoutmetric.New( + stdoutmetric.WithWriter(metricsFile), + stdoutmetric.WithPrettyPrint(), + ) + if err != nil { + return fmt.Errorf("failed to create stdout metric exporter: %w", err) + } + opts = append(opts, otelconf.WithMeterProviderOptions( + metricsdk.WithReader(metricsdk.NewPeriodicReader(exporter)), + )) + } + if extra.LogsFile != "" { + fmt.Printf("Initializing logs file: %s\n", extra.LogsFile) + logsFile, err := os.OpenFile(extra.LogsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("failed to open logs file: %w", err) + } + shutdownFuncs = append(shutdownFuncs, func(ctx context.Context) error { + if err := logsFile.Close(); err != nil { + return fmt.Errorf("failed to close logs file: %w", err) + } + return nil + }) + exporter, err := stdoutlog.New( + stdoutlog.WithWriter(logsFile), + stdoutlog.WithPrettyPrint(), + ) + if err != nil { + return fmt.Errorf("failed to create stdout log exporter: %w", err) + } + opts = append(opts, otelconf.WithLoggerProviderOptions( + logsdk.WithProcessor(logsdk.NewBatchProcessor(exporter)), + )) + } + if extra.InstrumentHost { + fmt.Println("Initializing host instrumentation") + if err := host.Start(); err != nil { + return fmt.Errorf("failed to start host instrumentation: %w", err) + } + } + if extra.InstrumentRuntime { + fmt.Println("Initializing runtime instrumentation") + if err := runtime.Start(); err != nil { + return fmt.Errorf("failed to start runtime instrumentation: %w", err) + } + } + } + } else { + fmt.Printf("failed to parse cosmos extra config: %v\n", err) + } } sdk, err = otelconf.NewSDK(opts...) if err != nil { - panic(fmt.Sprintf("failed to initialize telemetry: %v", err)) + return fmt.Errorf("failed to initialize telemetry: %w", err) } // setup otel global providers @@ -56,10 +165,32 @@ func init() { slog.SetDefault(otelslog.NewLogger("", otelslog.WithSource(true))) // emit an initialized message which verifies basic telemetry is working slog.Info("Telemetry initialized") + return nil +} + +type extraConfig struct { + CosmosExtra *cosmosExtra `json:"cosmos_extra" yaml:"cosmos_extra" mapstructure:"cosmos_extra"` +} + +type cosmosExtra struct { + TraceFile string `json:"trace_file" yaml:"trace_file" mapstructure:"trace_file"` + MetricsFile string `json:"metrics_file" yaml:"metrics_file" mapstructure:"metrics_file"` + LogsFile string `json:"logs_file" yaml:"logs_file" mapstructure:"logs_file"` + InstrumentHost bool `json:"instrument_host" yaml:"instrument_host" mapstructure:"instrument_host"` + InstrumentRuntime bool `json:"instrument_runtime" yaml:"instrument_runtime" mapstructure:"instrument_runtime"` } func Shutdown(ctx context.Context) error { - return sdk.Shutdown(ctx) + err := sdk.Shutdown(ctx) + if err != nil { + return fmt.Errorf("failed to shutdown telemetry: %w", err) + } + for _, f := range shutdownFuncs { + if err := f(ctx); err != nil { + return fmt.Errorf("failed to shutdown telemetry: %w", err) + } + } + return nil } func IsTelemetryEnabled() bool { diff --git a/tests/go.mod b/tests/go.mod index 6dc60d9ba6d8..b7cae937a344 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -100,6 +100,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -113,6 +114,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -158,6 +160,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -172,6 +175,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect @@ -183,6 +187,7 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -193,8 +198,11 @@ require ( github.com/supranational/blst v0.3.16 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tidwall/btree v1.8.1 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ulikunitz/xz v0.5.15 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/errs v1.4.0 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect @@ -204,7 +212,9 @@ require ( go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index 55cb1ce10c9a..a6914be4baf8 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -278,6 +278,8 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -343,6 +345,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= @@ -570,6 +575,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -692,6 +699,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -753,6 +762,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -815,6 +826,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -828,6 +843,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= @@ -851,8 +868,12 @@ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8F go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -1024,6 +1045,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1040,6 +1062,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1061,6 +1084,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index fcff5d1ed271..73c3a9d35f04 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -66,6 +66,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -76,6 +77,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -114,6 +116,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/highwayhash v1.0.3 // indirect @@ -125,6 +128,7 @@ require ( github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect @@ -136,6 +140,7 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -149,13 +154,18 @@ require ( github.com/tidwall/btree v1.8.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index e393aa40834a..ae2236127ba5 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -198,6 +198,8 @@ github.com/dvsekhvalnov/jose2go v1.8.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -252,6 +254,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -459,6 +464,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -575,6 +582,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -636,6 +645,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -702,6 +713,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -713,6 +728,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -730,6 +747,10 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -885,6 +906,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -901,6 +923,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -919,6 +942,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 0f8085aea5368283e5ca6eec6aa7ea8ff88d302a Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 18:32:57 -0500 Subject: [PATCH 64/87] add otel custom config options --- client/v2/go.mod | 10 ++++++++ client/v2/go.sum | 24 ++++++++++++++++++ telemetry/wrapper_test.go | 51 --------------------------------------- 3 files changed, 34 insertions(+), 51 deletions(-) delete mode 100644 telemetry/wrapper_test.go diff --git a/client/v2/go.mod b/client/v2/go.mod index 6e8810501d61..57e5cb55cc74 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -63,6 +63,7 @@ require ( github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -73,6 +74,7 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -111,6 +113,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.10.3 // indirect + github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -123,6 +126,7 @@ require ( github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.1 // indirect @@ -134,6 +138,7 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect @@ -143,13 +148,18 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.8.1 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zondax/golem v0.27.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v1.0.1 // indirect go.etcd.io/bbolt v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect go.opentelemetry.io/contrib/otelconf v0.18.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index 670ac1e03247..c4d65fca960a 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -200,6 +200,8 @@ github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= @@ -254,6 +256,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -461,6 +466,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.10.3 h1:0laII9AQ6kFxo5SjhdTfSh9EgF20piD6TMHK6YuDm+4= github.com/linxGnu/grocksdb v1.10.3/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= +github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -577,6 +584,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -638,6 +647,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw= github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -695,6 +706,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -706,6 +721,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= @@ -723,6 +740,10 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/bridges/otelslog v0.13.0 h1:bwnLpizECbPr1RrQ27waeY2SPIPeccCx/xLuoYADZ9s= go.opentelemetry.io/contrib/bridges/otelslog v0.13.0/go.mod h1:3nWlOiiqA9UtUnrcNk82mYasNxD8ehOspL0gOfEo6Y4= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0 h1:zsaUrWypCf0NtYSUby+/BS6QqhXVNxMQD5w4dLczKCQ= +go.opentelemetry.io/contrib/instrumentation/host v0.63.0/go.mod h1:Ru+kuFO+ToZqBKwI59rCStOhW6LWrbGisYrFaX61bJk= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= go.opentelemetry.io/contrib/otelconf v0.18.0 h1:ciF2Gf00BWs0DnexKFZXcxg9kJ8r3SUW1LOzW3CsKA8= go.opentelemetry.io/contrib/otelconf v0.18.0/go.mod h1:FcP7k+JLwBLdOxS6qY6VQ/4b5VBntI6L6o80IMwhAeI= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -878,6 +899,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -894,6 +916,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -913,6 +936,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/telemetry/wrapper_test.go b/telemetry/wrapper_test.go deleted file mode 100644 index 5388839874bc..000000000000 --- a/telemetry/wrapper_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package telemetry - -import ( - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -var mu sync.Mutex - -func initTelemetry(v bool) { - globalTelemetryEnabled = v -} - -// Reset the global state to a known disabled state before each test. -func setupTest(t *testing.T) { - t.Helper() - mu.Lock() // Ensure no other test can modify global state at the same time. - defer mu.Unlock() - initTelemetry(false) -} - -// TestNow tests the Now function when telemetry is enabled and disabled. -func TestNow(t *testing.T) { - setupTest(t) // Locks the mutex to avoid race condition. - - initTelemetry(true) - telemetryTime := Now() - assert.NotEqual(t, time.Time{}, telemetryTime, "Now() should not return zero time when telemetry is enabled") - - setupTest(t) // Reset the global state and lock the mutex again. - - initTelemetry(false) - telemetryTime = Now() - assert.Equal(t, time.Time{}, telemetryTime, "Now() should return zero time when telemetry is disabled") -} - -// TestIsTelemetryEnabled tests the IsTelemetryEnabled function. -func TestIsTelemetryEnabled(t *testing.T) { - setupTest(t) // Locks the mutex to avoid race condition. - - initTelemetry(true) - assert.True(t, IsTelemetryEnabled(), "IsTelemetryEnabled() should return true when globalTelemetryEnabled is set to true") - - setupTest(t) // Reset the global state and lock the mutex again. - - initTelemetry(false) - assert.False(t, IsTelemetryEnabled(), "IsTelemetryEnabled() should return false when globalTelemetryEnabled is set to false") -} From 03b60693e4648296fe5f8f9daa112fa07da86181 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 18:34:42 -0500 Subject: [PATCH 65/87] add more instrumentation --- baseapp/baseapp.go | 4 ++-- types/module/module.go | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 1b1df303cdef..89a74b748fc6 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -66,8 +66,8 @@ const ( var _ servertypes.ABCI = (*BaseApp)(nil) var ( - tracer = otel.Tracer("baseapp") - meter = otel.Meter("baseapp") + tracer = otel.Tracer("cosmos-sdk/baseapp") + meter = otel.Meter("cosmos-sdk/baseapp") blockCnt metric.Int64Counter txCnt metric.Int64Counter blockTime metric.Float64Histogram diff --git a/types/module/module.go b/types/module/module.go index 7d49b533748e..0f99847177e5 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -39,6 +39,8 @@ import ( abci "github.com/cometbft/cometbft/abci/types" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/genesis" @@ -52,6 +54,10 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) +var ( + tracer = otel.Tracer("cosmos-sdk/types/module") +) + // AppModuleBasic is the standard form for basic non-dependent elements of an application module. type AppModuleBasic interface { HasName @@ -752,8 +758,14 @@ func (m Manager) RunMigrations(ctx context.Context, cfg Configurator, fromVM Ver // It takes the current context as a parameter and returns a boolean value // indicating whether the migration was successfully executed or not. func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.PreBlock") + defer span.End() + paramsChanged := false for _, moduleName := range m.OrderPreBlockers { + var modSpan trace.Span + ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("PreBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasPreBlocker); ok { rsp, err := module.PreBlock(ctx) if err != nil { @@ -763,6 +775,7 @@ func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) { paramsChanged = true } } + modSpan.End() } return &sdk.ResponsePreBlock{ ConsensusParamsChanged: paramsChanged, @@ -773,13 +786,20 @@ func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) { // child context with an event manager to aggregate events emitted from all // modules. func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.BeginBlock") + defer span.End() + ctx = ctx.WithEventManager(sdk.NewEventManager()) for _, moduleName := range m.OrderBeginBlockers { + var modSpan trace.Span + ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("BeginBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasBeginBlocker); ok { if err := module.BeginBlock(ctx); err != nil { return sdk.BeginBlock{}, err } } + modSpan.End() } return sdk.BeginBlock{ @@ -791,10 +811,16 @@ func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) { // child context with an event manager to aggregate events emitted from all // modules. func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.EndBlock") + defer span.End() + ctx = ctx.WithEventManager(sdk.NewEventManager()) validatorUpdates := []abci.ValidatorUpdate{} for _, moduleName := range m.OrderEndBlockers { + var modSpan trace.Span + ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("EndBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasEndBlocker); ok { err := module.EndBlock(ctx) if err != nil { @@ -819,6 +845,7 @@ func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) { } else { continue } + modSpan.End() } return sdk.EndBlock{ From c4dbd070ab384e1c28733a019a3460809222c339 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 19:21:13 -0500 Subject: [PATCH 66/87] remove pretty print --- telemetry/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telemetry/config.go b/telemetry/config.go index 0bd329fb9828..dd34478cdd49 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -79,7 +79,7 @@ func doInit() error { }) exporter, err := stdouttrace.New( stdouttrace.WithWriter(traceFile), - stdouttrace.WithPrettyPrint(), + //stdouttrace.WithPrettyPrint(), ) if err != nil { return fmt.Errorf("failed to create stdout trace exporter: %w", err) @@ -102,7 +102,7 @@ func doInit() error { }) exporter, err := stdoutmetric.New( stdoutmetric.WithWriter(metricsFile), - stdoutmetric.WithPrettyPrint(), + //stdoutmetric.WithPrettyPrint(), ) if err != nil { return fmt.Errorf("failed to create stdout metric exporter: %w", err) @@ -125,7 +125,7 @@ func doInit() error { }) exporter, err := stdoutlog.New( stdoutlog.WithWriter(logsFile), - stdoutlog.WithPrettyPrint(), + //stdoutlog.WithPrettyPrint(), ) if err != nil { return fmt.Errorf("failed to create stdout log exporter: %w", err) From 4a94851aad6773e0338f9321bf08f15a510397d7 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 3 Nov 2025 19:34:50 -0500 Subject: [PATCH 67/87] go mod tidy --- client/v2/go.mod | 1 + client/v2/go.sum | 1 + go.sum | 1 + simapp/go.mod | 1 + simapp/go.sum | 1 + systemtests/go.mod | 1 + systemtests/go.sum | 1 + tests/go.mod | 1 + tests/go.sum | 1 + tests/systemtests/go.mod | 1 + tests/systemtests/go.sum | 1 + 11 files changed, 11 insertions(+) diff --git a/client/v2/go.mod b/client/v2/go.mod index 57e5cb55cc74..f1e15c4950ef 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -64,6 +64,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/client/v2/go.sum b/client/v2/go.sum index c4d65fca960a..f49a4efbad5a 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -202,6 +202,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/go.sum b/go.sum index 92042504ea99..9b07e99744e0 100644 --- a/go.sum +++ b/go.sum @@ -277,6 +277,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/simapp/go.mod b/simapp/go.mod index e87d8b3b03c6..51c3aa236923 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -102,6 +102,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index 6521c07eb831..53e1d7f6a024 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -288,6 +288,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/systemtests/go.mod b/systemtests/go.mod index 7230935f0337..4a461a8d408c 100644 --- a/systemtests/go.mod +++ b/systemtests/go.mod @@ -61,6 +61,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/systemtests/go.sum b/systemtests/go.sum index a4e3d7c18a49..283b7f5c1211 100644 --- a/systemtests/go.sum +++ b/systemtests/go.sum @@ -202,6 +202,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/tests/go.mod b/tests/go.mod index b7cae937a344..7d49eb8ad3c2 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -101,6 +101,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect diff --git a/tests/go.sum b/tests/go.sum index a6914be4baf8..4a4dac5350d0 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -280,6 +280,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index 73c3a9d35f04..2675fe39b853 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -67,6 +67,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.8.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect + github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index ae2236127ba5..5412a1fd1f6e 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -200,6 +200,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= From 3dde2cc6355852f75b62e82298ef0e1ac7e13653 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Nov 2025 12:39:20 -0500 Subject: [PATCH 68/87] improve instrumentation --- baseapp/abci.go | 38 +++++++++++++++++++++++++------------- baseapp/baseapp.go | 4 +++- baseapp/state/manager.go | 22 ++++++++++++++++++++++ baseapp/state/state.go | 3 +++ iavlx/cleanup.go | 24 +++++++++++++++++++----- iavlx/compactor.go | 14 ++++++-------- telemetry/config.go | 26 ++++++++++++++++++++------ types/module/module.go | 7 +++++++ 8 files changed, 105 insertions(+), 33 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index ae5f6f888818..c0ff059017d7 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -39,9 +39,6 @@ const ( ) func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { - _, span := tracer.Start(context.Background(), "InitChain") - defer span.End() - if req.ChainId != app.chainID { return nil, fmt.Errorf("invalid chain-id on InitChain; expected: %s, got: %s", app.chainID, req.ChainId) } @@ -109,10 +106,13 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha // add block gas meter for any genesis transactions (allow infinite gas) finalizeState.SetContext(finalizeState.Context().WithBlockGasMeter(storetypes.NewInfiniteGasMeter())) - res, err := app.abciHandlers.InitChainer(finalizeState.Context(), req) + ctx := finalizeState.Context() + ctx, span := ctx.StartSpan(tracer, "InitChain") + res, err := app.abciHandlers.InitChainer(ctx, req) if err != nil { return nil, err } + span.End() if len(req.Validators) > 0 { if len(req.Validators) != len(res.Validators) { @@ -771,12 +771,8 @@ func (app *BaseApp) internalFinalizeBlock(goCtx context.Context, req *abci.Reque app.stateManager.SetState(execModeFinalize, app.cms, header, app.logger, app.streamingManager) finalizeState = app.stateManager.GetState(execModeFinalize) } - ctx := finalizeState.Context().WithContext(goCtx) - ctx, span := ctx.StartSpan(tracer, "internalFinalizeBlock") - defer span.End() - // Context is now updated with Header information. - finalizeState.SetContext(ctx. + finalizeState.SetContext(finalizeState.Context(). WithBlockHeader(header). WithHeaderHash(req.Hash). WithHeaderInfo(coreheader.Info{ @@ -806,6 +802,16 @@ func (app *BaseApp) internalFinalizeBlock(goCtx context.Context, req *abci.Reque WithHeaderHash(req.Hash)) } + // instrument FinalizeBlock here and capture block context, because we'll switch back to that after FinalizeBlock + blockCtx := finalizeState.Context() + ctx, span := blockCtx.StartSpan(tracer, "FinalizeBlock") + finalizeState.SetContext(ctx) + defer span.End() + defer func() { + // restore the go context to block context so that Commit is not instrumented as a child of FinalizeBlock + finalizeState.SetContext(finalizeState.Context().WithContext(blockCtx.Context())) + }() + preblockEvents, err := app.preBlock(req) if err != nil { return nil, err @@ -820,9 +826,11 @@ func (app *BaseApp) internalFinalizeBlock(goCtx context.Context, req *abci.Reque // First check for an abort signal after beginBlock, as it's the first place // we spend any significant amount of time. + // NOTE: we use the go context here because that is used for cancellation + // whereas the finalize state context is used for block execution select { - case <-ctx.Done(): - return nil, ctx.Err() + case <-goCtx.Done(): + return nil, goCtx.Err() default: // continue } @@ -876,9 +884,11 @@ func (app *BaseApp) internalFinalizeBlock(goCtx context.Context, req *abci.Reque } // check after endBlock if we should abort, to avoid propagating the result + // NOTE: we use the go context here because that is used for cancellation + // whereas the finalize state context is used for block execution select { - case <-ctx.Done(): - return nil, ctx.Err() + case <-goCtx.Done(): + return nil, goCtx.Err() default: // continue } @@ -1033,6 +1043,8 @@ func (app *BaseApp) Commit() (*abci.ResponseCommit, error) { // The SnapshotIfApplicable method will create the snapshot by starting the goroutine app.snapshotManager.SnapshotIfApplicable(header.Height) + // track metrics and setup span for next block + // TODO: should this be in the state manager instead? blockCnt.Add(ctx, 1) blockTime.Record(ctx, time.Since(app.blockStartTime).Seconds()) app.blockStartTime = time.Now() diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 89a74b748fc6..a79138d7422b 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1,5 +1,8 @@ package baseapp +// need to import telemetry before anything else for side effects +import _ "github.com/cosmos/cosmos-sdk/telemetry" + import ( "fmt" "maps" @@ -36,7 +39,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/telemetry" - _ "github.com/cosmos/cosmos-sdk/telemetry" // need to initialize telemetry before we declare tracer and metrics sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/mempool" diff --git a/baseapp/state/manager.go b/baseapp/state/manager.go index fb8cb55e730f..7a7e585311db 100644 --- a/baseapp/state/manager.go +++ b/baseapp/state/manager.go @@ -5,6 +5,9 @@ import ( "sync" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "go.opentelemetry.io/otel" + otelattr "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "cosmossdk.io/core/header" "cosmossdk.io/log" @@ -14,6 +17,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var ( + tracer = otel.Tracer("cosmos-sdk/baseapp/state") +) + type Manager struct { // volatile states: // @@ -107,6 +114,17 @@ func (mgr *Manager) SetState( mgr.processProposalState = baseState case sdk.ExecModeFinalize: + // add tracing span instrumentation here when the context is initialized for the block + height := h.Height + if height == 0 { + height = 1 + } + baseState.ctx, baseState.span = baseState.ctx.StartSpan(tracer, "Block", + trace.WithAttributes( + otelattr.Int64("height", height), + otelattr.Int64("time_unix_nano", h.Time.UnixNano()), + ), + ) mgr.finalizeBlockState = baseState default: @@ -129,6 +147,10 @@ func (mgr *Manager) ClearState(mode sdk.ExecMode) { mgr.processProposalState = nil case sdk.ExecModeFinalize: + // complete tracing span instrumentation here when the context is cleared for the block + if mgr.finalizeBlockState != nil { + mgr.finalizeBlockState.span.End() + } mgr.finalizeBlockState = nil default: diff --git a/baseapp/state/state.go b/baseapp/state/state.go index fedfe5b1fd62..7655e8f83c12 100644 --- a/baseapp/state/state.go +++ b/baseapp/state/state.go @@ -4,6 +4,7 @@ import ( "sync" storetypes "cosmossdk.io/store/types" + "go.opentelemetry.io/otel/trace" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -13,6 +14,8 @@ type State struct { mtx sync.RWMutex ctx sdk.Context + + span trace.Span } func NewState(ctx sdk.Context, ms storetypes.CacheMultiStore) *State { diff --git a/iavlx/cleanup.go b/iavlx/cleanup.go index 12083a31eb5d..2c8021463a40 100644 --- a/iavlx/cleanup.go +++ b/iavlx/cleanup.go @@ -1,10 +1,18 @@ package iavlx import ( + "context" "errors" "fmt" + "log/slog" "sync" "time" + + "go.opentelemetry.io/otel" +) + +var ( + tracer = otel.Tracer("iavlx") ) type cleanupProc struct { @@ -42,7 +50,10 @@ func newCleanupProc(treeStore *TreeStore) *cleanupProc { } func (cp *cleanupProc) run() { + ctx, span := tracer.Start(context.Background(), "cleanupProc") + defer span.End() defer close(cp.cleanupProcDone) + minCompactorInterval := time.Second * time.Duration(cp.opts.MinCompactionSeconds) var lastCompactorStart time.Time @@ -80,7 +91,7 @@ func (cp *cleanupProc) run() { if i+1 < len(entries) { nextEntry = entries[i+1] } - err := cp.processEntry(entry, nextEntry) + err := cp.processEntry(ctx, entry, nextEntry) if err != nil { cp.logger.Error("failed to process changeset entry", "error", err) // on error, clean up any failed compaction and stop processing further entries this round @@ -172,7 +183,10 @@ func (cp *cleanupProc) doMarkOrphans() error { return nil } -func (cp *cleanupProc) processEntry(entry, nextEntry *changesetEntry) error { +func (cp *cleanupProc) processEntry(ctx context.Context, entry, nextEntry *changesetEntry) error { + ctx, span := tracer.Start(ctx, "cleanupProc.processEntry") + defer span.End() + cs := entry.changeset.Load() if cs.files == nil { @@ -208,7 +222,7 @@ func (cp *cleanupProc) processEntry(entry, nextEntry *changesetEntry) error { if cp.opts.CompactWAL && cs.TotalBytes()+cp.activeCompactor.TotalBytes() <= int(cp.opts.GetCompactionMaxTarget()) { // add to active compactor - cp.logger.Debug("joining changeset to active compactor", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir, + slog.DebugContext(ctx, "joining changeset to active compactor", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir, "newDir", cp.activeCompactor.files.dir) err = cp.activeCompactor.AddChangeset(cs) if err != nil { @@ -286,9 +300,9 @@ func (cp *cleanupProc) processEntry(entry, nextEntry *changesetEntry) error { } } - cp.logger.Info("compacting changeset", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir) + slog.Info("compacting changeset", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir) - cp.activeCompactor, err = NewCompacter(cp.logger, cs, CompactOptions{ + cp.activeCompactor, err = NewCompacter(ctx, cs, CompactOptions{ RetainCriteria: retainCriteria, CompactWAL: cp.opts.CompactWAL, CompactedAt: savedVersion, diff --git a/iavlx/compactor.go b/iavlx/compactor.go index e0fbb53326c1..0a90f48a3d12 100644 --- a/iavlx/compactor.go +++ b/iavlx/compactor.go @@ -1,10 +1,10 @@ package iavlx import ( + "context" "errors" "fmt" - - "cosmossdk.io/log" + "log/slog" ) type CompactOptions struct { @@ -16,8 +16,6 @@ type CompactOptions struct { type RetainCriteria func(createVersion, orphanVersion uint32) bool type Compactor struct { - logger log.Logger - criteria RetainCriteria compactWAL bool @@ -41,9 +39,10 @@ type Compactor struct { branchOrphanCount uint32 leafOrphanVersionTotal uint64 branchOrphanVersionTotal uint64 + ctx context.Context } -func NewCompacter(logger log.Logger, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { +func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { if reader.files == nil { return nil, fmt.Errorf("changeset has no associated files, cannot compact a shared changeset reader which files set to nil") } @@ -74,7 +73,7 @@ func NewCompacter(logger log.Logger, reader *Changeset, opts CompactOptions, sto } c := &Compactor{ - logger: logger, + ctx: ctx, criteria: opts.RetainCriteria, compactWAL: opts.CompactWAL, treeStore: store, @@ -110,9 +109,8 @@ func (c *Compactor) processChangeset(reader *Changeset) error { branchesData := reader.branchesData skippedBranches := 0 - c.logger.Debug("processing changeset for compaction", "versions", numVersions) + slog.DebugContext(c.ctx, "processing changeset for compaction", "versions", numVersions) for i := 0; i < numVersions; i++ { - c.logger.Debug("compacting version", "version", reader.info.StartVersion+uint32(i)) verInfo := *versionsData.UnsafeItem(uint32(i)) // copy newLeafStartIdx := uint32(0) newLeafEndIdx := uint32(0) diff --git a/telemetry/config.go b/telemetry/config.go index dd34478cdd49..ac16ee735aef 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "os" + "time" "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/contrib/instrumentation/host" @@ -107,8 +108,20 @@ func doInit() error { if err != nil { return fmt.Errorf("failed to create stdout metric exporter: %w", err) } + + // Configure periodic reader with custom interval if specified + readerOpts := []metricsdk.PeriodicReaderOption{} + if extra.MetricsFileInterval != "" { + interval, err := time.ParseDuration(extra.MetricsFileInterval) + if err != nil { + return fmt.Errorf("failed to parse metrics_file_interval: %w", err) + } + fmt.Printf("Configuring metrics export interval: %v\n", interval) + readerOpts = append(readerOpts, metricsdk.WithInterval(interval)) + } + opts = append(opts, otelconf.WithMeterProviderOptions( - metricsdk.WithReader(metricsdk.NewPeriodicReader(exporter)), + metricsdk.WithReader(metricsdk.NewPeriodicReader(exporter, readerOpts...)), )) } if extra.LogsFile != "" { @@ -173,11 +186,12 @@ type extraConfig struct { } type cosmosExtra struct { - TraceFile string `json:"trace_file" yaml:"trace_file" mapstructure:"trace_file"` - MetricsFile string `json:"metrics_file" yaml:"metrics_file" mapstructure:"metrics_file"` - LogsFile string `json:"logs_file" yaml:"logs_file" mapstructure:"logs_file"` - InstrumentHost bool `json:"instrument_host" yaml:"instrument_host" mapstructure:"instrument_host"` - InstrumentRuntime bool `json:"instrument_runtime" yaml:"instrument_runtime" mapstructure:"instrument_runtime"` + TraceFile string `json:"trace_file" yaml:"trace_file" mapstructure:"trace_file"` + MetricsFile string `json:"metrics_file" yaml:"metrics_file" mapstructure:"metrics_file"` + MetricsFileInterval string `json:"metrics_file_interval" yaml:"metrics_file_interval" mapstructure:"metrics_file_interval"` + LogsFile string `json:"logs_file" yaml:"logs_file" mapstructure:"logs_file"` + InstrumentHost bool `json:"instrument_host" yaml:"instrument_host" mapstructure:"instrument_host"` + InstrumentRuntime bool `json:"instrument_runtime" yaml:"instrument_runtime" mapstructure:"instrument_runtime"` } func Shutdown(ctx context.Context) error { diff --git a/types/module/module.go b/types/module/module.go index 0f99847177e5..f42643cb7819 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -486,6 +486,10 @@ func (m *Manager) RegisterServices(cfg Configurator) error { // module must return a non-empty validator set update to correctly initialize // the chain. func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) (*abci.ResponseInitChain, error) { + var span trace.Span + ctx, span = ctx.StartSpan(tracer, "Manager.InitGenesis") + defer span.End() + var validatorUpdates []abci.ValidatorUpdate ctx.Logger().Info("initializing blockchain state from genesis.json") for _, moduleName := range m.OrderInitGenesis { @@ -493,6 +497,8 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData continue } + var modSpan trace.Span + ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("InitGenesis.%s", moduleName)) mod := m.Modules[moduleName] // we might get an adapted module, a native core API module or a legacy module if module, ok := mod.(appmodule.HasGenesis); ok { @@ -523,6 +529,7 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData validatorUpdates = moduleValUpdates } } + modSpan.End() } // a chain must initialize with a non-empty validator set From 0041a8d6bf3183047e012a876c2ffe7d5d54b51e Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Nov 2025 12:50:02 -0500 Subject: [PATCH 69/87] fix instrumentation nesting --- baseapp/baseapp.go | 7 ++++--- types/module/module.go | 31 ++++++++++++++----------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index a79138d7422b..5184f62fef82 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -924,6 +924,8 @@ func (app *BaseApp) RunTx(mode sdk.ExecMode, txBytes []byte, tx sdk.Tx, txIndex anteCtx, anteSpan := anteCtx.StartSpan(tracer, "anteHandler") newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate) anteSpan.End() + // now we should back to the previous go context which didn't capture the anteHandler instrumentation span + newCtx = newCtx.WithContext(ctx) if !newCtx.IsZero() { // At this point, newCtx.MultiStore() is a store branch, or something else @@ -1045,14 +1047,14 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me break } - ctx = ctx.WithMsgIndex(i) + msgCtx := ctx.WithMsgIndex(i) handler := app.msgServiceRouter.Handler(msg) if handler == nil { return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) } - ctx, msgSpan := ctx.StartSpan(tracer, "msgHandler", + msgCtx, msgSpan := msgCtx.StartSpan(tracer, "msgHandler", trace.WithAttributes( attribute.String("msg_type", sdk.MsgTypeURL(msg)), attribute.Int("msg_index", i), @@ -1094,7 +1096,6 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me } msgResponses = append(msgResponses, msgResponse) } - } data, err := makeABCIData(msgResponses) diff --git a/types/module/module.go b/types/module/module.go index f42643cb7819..0aaf320c81de 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -497,28 +497,27 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData continue } - var modSpan trace.Span - ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("InitGenesis.%s", moduleName)) + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("InitGenesis.%s", moduleName)) mod := m.Modules[moduleName] // we might get an adapted module, a native core API module or a legacy module if module, ok := mod.(appmodule.HasGenesis); ok { - ctx.Logger().Debug("running initialization for module", "module", moduleName) + modCtx.Logger().Debug("running initialization for module", "module", moduleName) // core API genesis source, err := genesis.SourceFromRawJSON(genesisData[moduleName]) if err != nil { return &abci.ResponseInitChain{}, err } - err = module.InitGenesis(ctx, source) + err = module.InitGenesis(modCtx, source) if err != nil { return &abci.ResponseInitChain{}, err } } else if module, ok := mod.(HasGenesis); ok { - ctx.Logger().Debug("running initialization for module", "module", moduleName) - module.InitGenesis(ctx, cdc, genesisData[moduleName]) + modCtx.Logger().Debug("running initialization for module", "module", moduleName) + module.InitGenesis(modCtx, cdc, genesisData[moduleName]) } else if module, ok := mod.(HasABCIGenesis); ok { - ctx.Logger().Debug("running initialization for module", "module", moduleName) - moduleValUpdates := module.InitGenesis(ctx, cdc, genesisData[moduleName]) + modCtx.Logger().Debug("running initialization for module", "module", moduleName) + moduleValUpdates := module.InitGenesis(modCtx, cdc, genesisData[moduleName]) // use these validator updates if provided, the module manager assumes // only one module will update the validator set @@ -771,10 +770,9 @@ func (m *Manager) PreBlock(ctx sdk.Context) (*sdk.ResponsePreBlock, error) { paramsChanged := false for _, moduleName := range m.OrderPreBlockers { - var modSpan trace.Span - ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("PreBlock.%s", moduleName)) + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("PreBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasPreBlocker); ok { - rsp, err := module.PreBlock(ctx) + rsp, err := module.PreBlock(modCtx) if err != nil { return nil, err } @@ -800,9 +798,9 @@ func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) for _, moduleName := range m.OrderBeginBlockers { var modSpan trace.Span - ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("BeginBlock.%s", moduleName)) + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("BeginBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasBeginBlocker); ok { - if err := module.BeginBlock(ctx); err != nil { + if err := module.BeginBlock(modCtx); err != nil { return sdk.BeginBlock{}, err } } @@ -826,15 +824,14 @@ func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) { validatorUpdates := []abci.ValidatorUpdate{} for _, moduleName := range m.OrderEndBlockers { - var modSpan trace.Span - ctx, modSpan = ctx.StartSpan(tracer, fmt.Sprintf("EndBlock.%s", moduleName)) + modCtx, modSpan := ctx.StartSpan(tracer, fmt.Sprintf("EndBlock.%s", moduleName)) if module, ok := m.Modules[moduleName].(appmodule.HasEndBlocker); ok { - err := module.EndBlock(ctx) + err := module.EndBlock(modCtx) if err != nil { return sdk.EndBlock{}, err } } else if module, ok := m.Modules[moduleName].(HasABCIEndBlock); ok { - moduleValUpdates, err := module.EndBlock(ctx) + moduleValUpdates, err := module.EndBlock(modCtx) if err != nil { return sdk.EndBlock{}, err } From d64d65c6c7c973952822f9dbee74c568553c63a8 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Nov 2025 12:54:48 -0500 Subject: [PATCH 70/87] named msg handler spans --- baseapp/baseapp.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 5184f62fef82..2d0362b467f5 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1054,18 +1054,22 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Me return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) } + msgTypeUrl := sdk.MsgTypeURL(msg) + // we create two spans here for easy visualization in trace logs, this can be removed later if deemed unnecessary msgCtx, msgSpan := msgCtx.StartSpan(tracer, "msgHandler", trace.WithAttributes( - attribute.String("msg_type", sdk.MsgTypeURL(msg)), + attribute.String("msg_type", msgTypeUrl), attribute.Int("msg_index", i), ), ) + msgCtx, msgSpan2 := msgCtx.StartSpan(tracer, fmt.Sprintf("msgHandler.%s", msgTypeUrl)) // ADR 031 request type routing msgResult, err := handler(ctx, msg) + msgSpan2.End() + msgSpan.End() if err != nil { return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i) } - msgSpan.End() // create message events msgEvents, err := createEvents(app.cdc, msgResult.GetEvents(), msg, msgsV2[i]) From 8261955ea98d37486f3f57086f4075d9dfea7824 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Nov 2025 14:08:40 -0500 Subject: [PATCH 71/87] add basic python timing analysis --- .python-version | 1 + analysis/__init__.py | 0 analysis/analysis.ipynb | 977 ++++++++++++++++++++++ analysis/analysis.py | 124 +++ analysis/read_otel.py | 128 +++ baseapp/state/manager.go | 2 +- pyproject.toml | 13 + uv.lock | 1650 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 2894 insertions(+), 1 deletion(-) create mode 100644 .python-version create mode 100644 analysis/__init__.py create mode 100644 analysis/analysis.ipynb create mode 100644 analysis/analysis.py create mode 100644 analysis/read_otel.py create mode 100644 pyproject.toml create mode 100644 uv.lock diff --git a/.python-version b/.python-version new file mode 100644 index 000000000000..24ee5b1be996 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/analysis/__init__.py b/analysis/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/analysis/analysis.ipynb b/analysis/analysis.ipynb new file mode 100644 index 000000000000..3a899d9e8503 --- /dev/null +++ b/analysis/analysis.ipynb @@ -0,0 +1,977 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2025-11-04T19:06:32.346896Z", + "start_time": "2025-11-04T19:06:29.902440Z" + } + }, + "source": [ + "from analysis.read_otel import load_otel_runs\n", + "from analysis.analysis import block_summary, plot_block_durations\n", + "import pandas as pd" + ], + "outputs": [], + "execution_count": 1 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-04T19:06:33.304373Z", + "start_time": "2025-11-04T19:06:32.556778Z" + } + }, + "cell_type": "code", + "source": "runs = load_otel_runs(\"/Users/arc/iavl-bench-data/sims\")", + "id": "1d7a28bf36057a9d", + "outputs": [], + "execution_count": 2 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-04T19:06:35.205099Z", + "start_time": "2025-11-04T19:06:33.320602Z" + } + }, + "cell_type": "code", + "source": [ + "pd.DataFrame(\n", + " [{'run': name, 'duration': block_summary(run).total_duration_seconds} for name, run in runs.items()]).set_index(\n", + " 'run')\n" + ], + "id": "7eee7d20b6a3ddfc", + "outputs": [ + { + "data": { + "text/plain": [ + " duration\n", + "run \n", + "iavl1 208.354362\n", + "iavlx 178.002386" + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
duration
run
iavl1208.354362
iavlx178.002386
\n", + "
" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 3 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-04T19:06:39.439986Z", + "start_time": "2025-11-04T19:06:35.251031Z" + } + }, + "cell_type": "code", + "source": "plot_block_durations(runs)", + "id": "f8f479875319ace8", + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "data": [ + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavl1", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + } + ], + "layout": { + "template": { + "data": { + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scattermap": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermap" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "#C8D4E3" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "baxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Block Duration Comparison" + }, + "xaxis": { + "title": { + "text": "Block Number" + } + }, + "yaxis": { + "title": { + "text": "Duration (ms)" + } + }, + "hovermode": "x unified" + }, + "config": { + "plotlyServerURL": "https://plot.ly" + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 4 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/analysis/analysis.py b/analysis/analysis.py new file mode 100644 index 000000000000..8a9cac903cae --- /dev/null +++ b/analysis/analysis.py @@ -0,0 +1,124 @@ +"""Analysis functions for OpenTelemetry trace and metrics data.""" + +import duckdb +from datetime import datetime +from pydantic import BaseModel +import pandas as pd +import plotly.graph_objects as go +from typing import Dict + +# Type alias for runs dictionary (run_name -> DuckDB connection) +Runs = Dict[str, duckdb.DuckDBPyConnection] + + +class BlockSummary(BaseModel): + """Summary of block execution.""" + start_time: datetime + end_time: datetime + total_duration_seconds: float + block_count: int + + +def block_summary(con: duckdb.DuckDBPyConnection) -> BlockSummary: + """ + Generate a high-level summary of block execution. + + Args: + con: DuckDB connection with spans view loaded + + Returns: + BlockSummary object with: + - start_time: Start time of first block + - end_time: End time of last block + - total_duration_seconds: Total duration from first block start to last block end + - block_count: Total number of blocks processed + + Example: + >>> from read_otel import load_otel_data + >>> con = load_otel_data('/path/to/data') + >>> summary = block_summary(con) + >>> print(f"Processed {summary.block_count} blocks in {summary.total_duration_seconds:.2f}s") + """ + result = con.sql(""" + SELECT + MIN(start_time) AS start_time, + MAX(end_time) AS end_time, + EXTRACT(EPOCH FROM (MAX(end_time) - MIN(start_time))) AS total_duration_seconds, + COUNT(*) AS block_count + FROM spans + WHERE span_name = 'Block' AND scope = 'cosmos-sdk/baseapp' + """).fetchone() + + return BlockSummary( + start_time=result[0], + end_time=result[1], + total_duration_seconds=result[2], + block_count=result[3], + ) + + +def block_durations(con: duckdb.DuckDBPyConnection) -> pd.DataFrame: + """ + Get duration for each block. + + Args: + con: DuckDB connection with spans view loaded + + Returns: + DataFrame with columns: + - block_number: Sequential block number (1-indexed) + - duration_ms: Block duration in milliseconds + + Example: + >>> from read_otel import load_otel_data + >>> con = load_otel_data('/path/to/data') + >>> df = block_durations(con) + >>> df.head() + """ + return con.sql(""" + SELECT + ROW_NUMBER() OVER (ORDER BY start_time) AS block_number, + EXTRACT(EPOCH FROM duration) * 1000 AS duration_ms + FROM spans + WHERE span_name = 'Block' AND scope = 'cosmos-sdk/baseapp' + ORDER BY start_time + """).df() + + +def plot_block_durations(runs: Runs) -> go.Figure: + """ + Create a plotly line chart comparing block durations across runs. + + Args: + runs: Dictionary mapping run name to DuckDB connection + + Returns: + Plotly Figure object with block duration traces for each run + + Example: + >>> from read_otel import load_otel_runs + >>> runs = load_otel_runs('/path/to/data') + >>> fig = plot_block_durations(runs) + >>> fig.show() + """ + fig = go.Figure() + + for run_name, con in runs.items(): + df = block_durations(con) + fig.add_trace(go.Scatter( + x=df['block_number'], + y=df['duration_ms'], + mode='lines', + name=run_name, + line=dict(width=2) + )) + + fig.update_layout( + title='Block Duration Comparison', + xaxis_title='Block Number', + yaxis_title='Duration (ms)', + hovermode='x unified', + template='plotly_white' + ) + + return fig \ No newline at end of file diff --git a/analysis/read_otel.py b/analysis/read_otel.py new file mode 100644 index 000000000000..179e2c3e9cd7 --- /dev/null +++ b/analysis/read_otel.py @@ -0,0 +1,128 @@ +"""Load OpenTelemetry JSONL data into DuckDB views.""" + +import duckdb +from pathlib import Path +from typing import Dict + + +def load_otel_data(data_path: str | Path) -> duckdb.DuckDBPyConnection: + """ + Load OpenTelemetry data from a directory containing trace.jsonl, logs.jsonl, and metrics.jsonl. + + Creates three views in the returned DuckDB connection: + - spans: flattened trace data + - logs: flattened log data + - metrics: flattened metrics data + + Args: + data_path: Path to directory containing the JSONL files + + Returns: + DuckDB connection with views created + + Example: + >>> con = load_otel_data('/path/to/data') + >>> con.sql("SELECT span_name, count(*) FROM spans GROUP BY span_name").show() + """ + data_path = Path(data_path) + con = duckdb.connect(':memory:') + + # Create spans view from trace.jsonl + trace_file = data_path / 'trace.jsonl' + if trace_file.exists(): + con.execute(f""" + CREATE VIEW spans AS + SELECT + Name AS span_name, + SpanContext.TraceID AS trace_id, + SpanContext.SpanID AS span_id, + Parent.SpanID AS parent_span_id, + CAST(StartTime AS TIMESTAMPTZ) AS start_time, + CAST(EndTime AS TIMESTAMPTZ) AS end_time, + CAST(EndTime AS TIMESTAMPTZ) - CAST(StartTime AS TIMESTAMPTZ) AS duration, + InstrumentationScope.Name AS scope, + ChildSpanCount AS child_span_count, + Attributes, + Resource, + Status + FROM read_ndjson_auto('{trace_file}') + """) + + # Create logs view from logs.jsonl + logs_file = data_path / 'logs.jsonl' + if logs_file.exists(): + con.execute(f""" + CREATE VIEW logs AS + SELECT + CAST(Timestamp AS TIMESTAMPTZ) AS timestamp, + CAST(ObservedTimestamp AS TIMESTAMPTZ) AS observed_timestamp, + Severity AS severity, + SeverityText AS severity_text, + Body.Value AS body, + TraceID AS trace_id, + SpanID AS span_id, + Attributes, + Resource, + Scope + FROM read_ndjson_auto('{logs_file}') + """) + + # Create metrics view from metrics.jsonl (if file exists and has data) + metrics_file = data_path / 'metrics.jsonl' + if metrics_file.exists() and metrics_file.stat().st_size > 0: + # Flatten the deeply nested metrics structure + # Structure: Resource → ScopeMetrics[] → Metrics[] → Data → DataPoints[] + con.execute(f""" + CREATE VIEW metrics AS + SELECT + scope_metric.Scope.Name AS scope_name, + scope_metric.Scope.Version AS scope_version, + metric.Name AS metric_name, + metric.Description AS metric_description, + metric.Unit AS unit, + metric.Data.Temporality AS temporality, + metric.Data.IsMonotonic AS is_monotonic, + CAST(dp.Time AS TIMESTAMPTZ) AS time, + CAST(dp.StartTime AS TIMESTAMPTZ) AS start_time, + dp.Value AS value, + dp.Count AS count, + dp.Sum AS sum, + dp.Min AS min, + dp.Max AS max, + dp.Attributes AS attributes, + raw.Resource AS resource + FROM read_ndjson_auto('{metrics_file}') AS raw + CROSS JOIN UNNEST(raw.ScopeMetrics) AS t(scope_metric) + CROSS JOIN UNNEST(scope_metric.Metrics) AS t2(metric) + CROSS JOIN UNNEST(metric.Data.DataPoints) AS t3(dp) + """) + + return con + + +def load_otel_runs(runs_dir: str | Path) -> Dict[str, duckdb.DuckDBPyConnection]: + """ + Load OpenTelemetry data from multiple run directories. + + Args: + runs_dir: Path to directory containing subdirectories with OTEL data + Each subdirectory should contain trace.jsonl, logs.jsonl, metrics.jsonl + + Returns: + Dictionary mapping run name (subdirectory name) to DuckDB connection + + Example: + >>> connections = load_otel_runs('/Users/arc/iavl-bench-data/sims') + >>> # Returns: {'iavlx': , 'iavl1': } + >>> connections['iavlx'].sql("SELECT count(*) FROM spans").show() + """ + runs_dir = Path(runs_dir) + connections = {} + + for subdir in runs_dir.iterdir(): + if subdir.is_dir(): + # Check if it has trace.jsonl to confirm it's a valid run directory + if (subdir / 'trace.jsonl').exists(): + connections[subdir.name] = load_otel_data(subdir) + + return connections \ No newline at end of file diff --git a/baseapp/state/manager.go b/baseapp/state/manager.go index 7a7e585311db..e17b55547310 100644 --- a/baseapp/state/manager.go +++ b/baseapp/state/manager.go @@ -18,7 +18,7 @@ import ( ) var ( - tracer = otel.Tracer("cosmos-sdk/baseapp/state") + tracer = otel.Tracer("cosmos-sdk/baseapp") ) type Manager struct { diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000000..0702e8609b8f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "cosmos-sdk" +version = "0.1.0" +description = "Add your description here" +requires-python = ">=3.13" +dependencies = [ + "duckdb>=1.4.1", + "notebook>=7.4.7", + "pandas>=2.3.3", + "plotly>=6.4.0", + "pydantic>=2.12.3", + "pytz>=2025.2", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000000..07704c9c62b0 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1650 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "async-lru" +version = "2.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "cosmos-sdk" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "duckdb" }, + { name = "notebook" }, + { name = "pandas" }, + { name = "plotly" }, + { name = "pydantic" }, + { name = "pytz" }, +] + +[package.metadata] +requires-dist = [ + { name = "duckdb", specifier = ">=1.4.1" }, + { name = "notebook", specifier = ">=7.4.7" }, + { name = "pandas", specifier = ">=2.3.3" }, + { name = "plotly", specifier = ">=6.4.0" }, + { name = "pydantic", specifier = ">=2.12.3" }, + { name = "pytz", specifier = ">=2025.2" }, +] + +[[package]] +name = "debugpy" +version = "1.8.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386, upload-time = "2025-09-17T16:33:54.594Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100, upload-time = "2025-09-17T16:33:56.353Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002, upload-time = "2025-09-17T16:33:58.231Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047, upload-time = "2025-09-17T16:34:00.586Z" }, + { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899, upload-time = "2025-09-17T16:34:02.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254, upload-time = "2025-09-17T16:34:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203, upload-time = "2025-09-17T16:34:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493, upload-time = "2025-09-17T16:34:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "duckdb" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/e7/21cf50a3d52ffceee1f0bcc3997fa96a5062e6bab705baee4f6c4e33cce5/duckdb-1.4.1.tar.gz", hash = "sha256:f903882f045d057ebccad12ac69975952832edfe133697694854bb784b8d6c76", size = 18461687, upload-time = "2025-10-07T10:37:28.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/08/705988c33e38665c969f7876b3ca4328be578554aa7e3dc0f34158da3e64/duckdb-1.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:46496a2518752ae0c6c5d75d4cdecf56ea23dd098746391176dd8e42cf157791", size = 29077070, upload-time = "2025-10-07T10:36:59.83Z" }, + { url = "https://files.pythonhosted.org/packages/99/c5/7c9165f1e6b9069441bcda4da1e19382d4a2357783d37ff9ae238c5c41ac/duckdb-1.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1c65ae7e9b541cea07d8075343bcfebdecc29a3c0481aa6078ee63d51951cfcd", size = 16167506, upload-time = "2025-10-07T10:37:02.24Z" }, + { url = "https://files.pythonhosted.org/packages/38/46/267f4a570a0ee3ae6871ddc03435f9942884284e22a7ba9b7cb252ee69b6/duckdb-1.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:598d1a314e34b65d9399ddd066ccce1eeab6a60a2ef5885a84ce5ed62dbaf729", size = 13762330, upload-time = "2025-10-07T10:37:04.581Z" }, + { url = "https://files.pythonhosted.org/packages/15/7b/c4f272a40c36d82df20937d93a1780eb39ab0107fe42b62cba889151eab9/duckdb-1.4.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2f16b8def782d484a9f035fc422bb6f06941ed0054b4511ddcdc514a7fb6a75", size = 18504687, upload-time = "2025-10-07T10:37:06.991Z" }, + { url = "https://files.pythonhosted.org/packages/17/fc/9b958751f0116d7b0406406b07fa6f5a10c22d699be27826d0b896f9bf51/duckdb-1.4.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a7d0aed068a5c33622a8848857947cab5cfb3f2a315b1251849bac2c74c492", size = 20513823, upload-time = "2025-10-07T10:37:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/79/4f544d73fcc0513b71296cb3ebb28a227d22e80dec27204977039b9fa875/duckdb-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:280fd663dacdd12bb3c3bf41f3e5b2e5b95e00b88120afabb8b8befa5f335c6f", size = 12336460, upload-time = "2025-10-07T10:37:12.154Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, +] + +[[package]] +name = "ipython" +version = "9.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/34/29b18c62e39ee2f7a6a3bba7efd952729d8aadd45ca17efc34453b717665/ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731", size = 4396932, upload-time = "2025-09-29T10:55:53.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196", size = 616170, upload-time = "2025-09-29T10:55:47.676Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "json5" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "rfc3987-syntax" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, +] + +[[package]] +name = "jupyter-server" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" }, +] + +[[package]] +name = "jupyterlab" +version = "4.4.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/5d/75c42a48ff5fc826a7dff3fe4004cda47c54f9d981c351efacfbc9139d3c/jupyterlab-4.4.10.tar.gz", hash = "sha256:521c017508af4e1d6d9d8a9d90f47a11c61197ad63b2178342489de42540a615", size = 22969303, upload-time = "2025-10-22T14:50:58.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl", hash = "sha256:65939ab4c8dcd0c42185c2d0d1a9d60b254dc8c46fc4fdb286b63c51e9358e07", size = 12293385, upload-time = "2025-10-22T14:50:54.075Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, +] + +[[package]] +name = "lark" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mistune" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588, upload-time = "2025-08-29T07:20:43.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" }, +] + +[[package]] +name = "narwhals" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/dc/8db74daf8c2690ec696c1d772a33cc01511559ee8a9e92d7ed85a18e3c22/narwhals-2.10.2.tar.gz", hash = "sha256:ff738a08bc993cbb792266bec15346c1d85cc68fdfe82a23283c3713f78bd354", size = 584954, upload-time = "2025-11-04T16:36:42.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/a9/9e02fa97e421a355fc5e818e9c488080fce04a8e0eebb3ed75a84f041c4a/narwhals-2.10.2-py3-none-any.whl", hash = "sha256:059cd5c6751161b97baedcaf17a514c972af6a70f36a89af17de1a0caf519c43", size = 419573, upload-time = "2025-11-04T16:36:40.574Z" }, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "notebook" +version = "7.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/09/f6f64ba156842ef68d3ea763fa171a2f7e7224f200a15dd4af5b83c34756/notebook-7.4.7.tar.gz", hash = "sha256:3f0a04027dfcee8a876de48fba13ab77ec8c12f72f848a222ed7f5081b9e342a", size = 13937702, upload-time = "2025-09-27T08:00:22.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/d7/06d13087e20388926e7423d2489e728d2e59f2453039cdb0574a7c070e76/notebook-7.4.7-py3-none-any.whl", hash = "sha256:362b7c95527f7dd3c4c84d410b782872fd9c734fb2524c11dd92758527b6eda6", size = 14342894, upload-time = "2025-09-27T08:00:18.496Z" }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, + { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, + { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, + { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, + { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "plotly" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/e6/b768650072837505804bed4790c5449ba348a3b720e27ca7605414e998cd/plotly-6.4.0.tar.gz", hash = "sha256:68c6db2ed2180289ef978f087841148b7efda687552276da15a6e9b92107052a", size = 7012379, upload-time = "2025-11-04T17:59:26.45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/ae/89b45ccccfeebc464c9233de5675990f75241b8ee4cd63227800fdf577d1/plotly-6.4.0-py3-none-any.whl", hash = "sha256:a1062eafbdc657976c2eedd276c90e184ccd6c21282a5e9ee8f20efca9c9a4c5", size = 9892458, upload-time = "2025-11-04T17:59:22.622Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, + { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, + { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, + { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywinpty" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" }, + { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386, upload-time = "2025-10-03T21:18:50.477Z" }, + { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834, upload-time = "2025-10-03T21:19:25.688Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, +] + +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/dc/95f074d43452b3ef5d06276696ece4b3b5d696e7c9ad7173c54b1390cd70/rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea", size = 27419, upload-time = "2025-10-22T22:24:29.327Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/03/ce566d92611dfac0085c2f4b048cd53ed7c274a5c05974b882a908d540a2/rpds_py-0.28.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e9e184408a0297086f880556b6168fa927d677716f83d3472ea333b42171ee3b", size = 366235, upload-time = "2025-10-22T22:22:28.397Z" }, + { url = "https://files.pythonhosted.org/packages/00/34/1c61da1b25592b86fd285bd7bd8422f4c9d748a7373b46126f9ae792a004/rpds_py-0.28.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:edd267266a9b0448f33dc465a97cfc5d467594b600fe28e7fa2f36450e03053a", size = 348241, upload-time = "2025-10-22T22:22:30.171Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/ed1e28616848c61c493a067779633ebf4b569eccaacf9ccbdc0e7cba2b9d/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85beb8b3f45e4e32f6802fb6cd6b17f615ef6c6a52f265371fb916fae02814aa", size = 378079, upload-time = "2025-10-22T22:22:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/ccb30333a16a470091b6e50289adb4d3ec656fd9951ba8c5e3aaa0746a67/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2412be8d00a1b895f8ad827cc2116455196e20ed994bb704bf138fe91a42724", size = 393151, upload-time = "2025-10-22T22:22:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d0/73e2217c3ee486d555cb84920597480627d8c0240ff3062005c6cc47773e/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf128350d384b777da0e68796afdcebc2e9f63f0e9f242217754e647f6d32491", size = 517520, upload-time = "2025-10-22T22:22:34.949Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/23efe81c700427d0841a4ae7ea23e305654381831e6029499fe80be8a071/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2036d09b363aa36695d1cc1a97b36865597f4478470b0697b5ee9403f4fe399", size = 408699, upload-time = "2025-10-22T22:22:36.584Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ee/a324d3198da151820a326c1f988caaa4f37fc27955148a76fff7a2d787a9/rpds_py-0.28.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e1e9be4fa6305a16be628959188e4fd5cd6f1b0e724d63c6d8b2a8adf74ea6", size = 385720, upload-time = "2025-10-22T22:22:38.014Z" }, + { url = "https://files.pythonhosted.org/packages/19/ad/e68120dc05af8b7cab4a789fccd8cdcf0fe7e6581461038cc5c164cd97d2/rpds_py-0.28.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0a403460c9dd91a7f23fc3188de6d8977f1d9603a351d5db6cf20aaea95b538d", size = 401096, upload-time = "2025-10-22T22:22:39.869Z" }, + { url = "https://files.pythonhosted.org/packages/99/90/c1e070620042459d60df6356b666bb1f62198a89d68881816a7ed121595a/rpds_py-0.28.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d7366b6553cdc805abcc512b849a519167db8f5e5c3472010cd1228b224265cb", size = 411465, upload-time = "2025-10-22T22:22:41.395Z" }, + { url = "https://files.pythonhosted.org/packages/68/61/7c195b30d57f1b8d5970f600efee72a4fad79ec829057972e13a0370fd24/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b43c6a3726efd50f18d8120ec0551241c38785b68952d240c45ea553912ac41", size = 558832, upload-time = "2025-10-22T22:22:42.871Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3d/06f3a718864773f69941d4deccdf18e5e47dd298b4628062f004c10f3b34/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0cb7203c7bc69d7c1585ebb33a2e6074492d2fc21ad28a7b9d40457ac2a51ab7", size = 583230, upload-time = "2025-10-22T22:22:44.877Z" }, + { url = "https://files.pythonhosted.org/packages/66/df/62fc783781a121e77fee9a21ead0a926f1b652280a33f5956a5e7833ed30/rpds_py-0.28.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a52a5169c664dfb495882adc75c304ae1d50df552fbd68e100fdc719dee4ff9", size = 553268, upload-time = "2025-10-22T22:22:46.441Z" }, + { url = "https://files.pythonhosted.org/packages/84/85/d34366e335140a4837902d3dea89b51f087bd6a63c993ebdff59e93ee61d/rpds_py-0.28.0-cp313-cp313-win32.whl", hash = "sha256:2e42456917b6687215b3e606ab46aa6bca040c77af7df9a08a6dcfe8a4d10ca5", size = 217100, upload-time = "2025-10-22T22:22:48.342Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1c/f25a3f3752ad7601476e3eff395fe075e0f7813fbb9862bd67c82440e880/rpds_py-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:e0a0311caedc8069d68fc2bf4c9019b58a2d5ce3cd7cb656c845f1615b577e1e", size = 227759, upload-time = "2025-10-22T22:22:50.219Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d6/5f39b42b99615b5bc2f36ab90423ea404830bdfee1c706820943e9a645eb/rpds_py-0.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:04c1b207ab8b581108801528d59ad80aa83bb170b35b0ddffb29c20e411acdc1", size = 217326, upload-time = "2025-10-22T22:22:51.647Z" }, + { url = "https://files.pythonhosted.org/packages/5c/8b/0c69b72d1cee20a63db534be0df271effe715ef6c744fdf1ff23bb2b0b1c/rpds_py-0.28.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f296ea3054e11fc58ad42e850e8b75c62d9a93a9f981ad04b2e5ae7d2186ff9c", size = 355736, upload-time = "2025-10-22T22:22:53.211Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6d/0c2ee773cfb55c31a8514d2cece856dd299170a49babd50dcffb15ddc749/rpds_py-0.28.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5a7306c19b19005ad98468fcefeb7100b19c79fc23a5f24a12e06d91181193fa", size = 342677, upload-time = "2025-10-22T22:22:54.723Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1c/22513ab25a27ea205144414724743e305e8153e6abe81833b5e678650f5a/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5d9b86aa501fed9862a443c5c3116f6ead8bc9296185f369277c42542bd646b", size = 371847, upload-time = "2025-10-22T22:22:56.295Z" }, + { url = "https://files.pythonhosted.org/packages/60/07/68e6ccdb4b05115ffe61d31afc94adef1833d3a72f76c9632d4d90d67954/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5bbc701eff140ba0e872691d573b3d5d30059ea26e5785acba9132d10c8c31d", size = 381800, upload-time = "2025-10-22T22:22:57.808Z" }, + { url = "https://files.pythonhosted.org/packages/73/bf/6d6d15df80781d7f9f368e7c1a00caf764436518c4877fb28b029c4624af/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5690671cd672a45aa8616d7374fdf334a1b9c04a0cac3c854b1136e92374fe", size = 518827, upload-time = "2025-10-22T22:22:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d3/2decbb2976cc452cbf12a2b0aaac5f1b9dc5dd9d1f7e2509a3ee00421249/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f1d92ecea4fa12f978a367c32a5375a1982834649cdb96539dcdc12e609ab1a", size = 399471, upload-time = "2025-10-22T22:23:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2c/f30892f9e54bd02e5faca3f6a26d6933c51055e67d54818af90abed9748e/rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d252db6b1a78d0a3928b6190156042d54c93660ce4d98290d7b16b5296fb7cc", size = 377578, upload-time = "2025-10-22T22:23:03.52Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5d/3bce97e5534157318f29ac06bf2d279dae2674ec12f7cb9c12739cee64d8/rpds_py-0.28.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d61b355c3275acb825f8777d6c4505f42b5007e357af500939d4a35b19177259", size = 390482, upload-time = "2025-10-22T22:23:05.391Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f0/886bd515ed457b5bd93b166175edb80a0b21a210c10e993392127f1e3931/rpds_py-0.28.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:acbe5e8b1026c0c580d0321c8aae4b0a1e1676861d48d6e8c6586625055b606a", size = 402447, upload-time = "2025-10-22T22:23:06.93Z" }, + { url = "https://files.pythonhosted.org/packages/42/b5/71e8777ac55e6af1f4f1c05b47542a1eaa6c33c1cf0d300dca6a1c6e159a/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8aa23b6f0fc59b85b4c7d89ba2965af274346f738e8d9fc2455763602e62fd5f", size = 552385, upload-time = "2025-10-22T22:23:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cb/6ca2d70cbda5a8e36605e7788c4aa3bea7c17d71d213465a5a675079b98d/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7b14b0c680286958817c22d76fcbca4800ddacef6f678f3a7c79a1fe7067fe37", size = 575642, upload-time = "2025-10-22T22:23:10.348Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d4/407ad9960ca7856d7b25c96dcbe019270b5ffdd83a561787bc682c797086/rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bcf1d210dfee61a6c86551d67ee1031899c0fdbae88b2d44a569995d43797712", size = 544507, upload-time = "2025-10-22T22:23:12.434Z" }, + { url = "https://files.pythonhosted.org/packages/51/31/2f46fe0efcac23fbf5797c6b6b7e1c76f7d60773e525cb65fcbc582ee0f2/rpds_py-0.28.0-cp313-cp313t-win32.whl", hash = "sha256:3aa4dc0fdab4a7029ac63959a3ccf4ed605fee048ba67ce89ca3168da34a1342", size = 205376, upload-time = "2025-10-22T22:23:13.979Z" }, + { url = "https://files.pythonhosted.org/packages/92/e4/15947bda33cbedfc134490a41841ab8870a72a867a03d4969d886f6594a2/rpds_py-0.28.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7b7d9d83c942855e4fdcfa75d4f96f6b9e272d42fffcb72cd4bb2577db2e2907", size = 215907, upload-time = "2025-10-22T22:23:15.5Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/ffe8cd7a6a02833b10623bf765fbb57ce977e9a4318ca0e8cf97e9c3d2b3/rpds_py-0.28.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:dcdcb890b3ada98a03f9f2bb108489cdc7580176cb73b4f2d789e9a1dac1d472", size = 353830, upload-time = "2025-10-22T22:23:17.03Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9f/890f36cbd83a58491d0d91ae0db1702639edb33fb48eeb356f80ecc6b000/rpds_py-0.28.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f274f56a926ba2dc02976ca5b11c32855cbd5925534e57cfe1fda64e04d1add2", size = 341819, upload-time = "2025-10-22T22:23:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/09/e3/921eb109f682aa24fb76207698fbbcf9418738f35a40c21652c29053f23d/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe0438ac4a29a520ea94c8c7f1754cdd8feb1bc490dfda1bfd990072363d527", size = 373127, upload-time = "2025-10-22T22:23:20.216Z" }, + { url = "https://files.pythonhosted.org/packages/23/13/bce4384d9f8f4989f1a9599c71b7a2d877462e5fd7175e1f69b398f729f4/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a358a32dd3ae50e933347889b6af9a1bdf207ba5d1a3f34e1a38cd3540e6733", size = 382767, upload-time = "2025-10-22T22:23:21.787Z" }, + { url = "https://files.pythonhosted.org/packages/23/e1/579512b2d89a77c64ccef5a0bc46a6ef7f72ae0cf03d4b26dcd52e57ee0a/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e80848a71c78aa328fefaba9c244d588a342c8e03bda518447b624ea64d1ff56", size = 517585, upload-time = "2025-10-22T22:23:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/62/3c/ca704b8d324a2591b0b0adcfcaadf9c862375b11f2f667ac03c61b4fd0a6/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f586db2e209d54fe177e58e0bc4946bea5fb0102f150b1b2f13de03e1f0976f8", size = 399828, upload-time = "2025-10-22T22:23:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/da/37/e84283b9e897e3adc46b4c88bb3f6ec92a43bd4d2f7ef5b13459963b2e9c/rpds_py-0.28.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ae8ee156d6b586e4292491e885d41483136ab994e719a13458055bec14cf370", size = 375509, upload-time = "2025-10-22T22:23:27.32Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c2/a980beab869d86258bf76ec42dec778ba98151f253a952b02fe36d72b29c/rpds_py-0.28.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a805e9b3973f7e27f7cab63a6b4f61d90f2e5557cff73b6e97cd5b8540276d3d", size = 392014, upload-time = "2025-10-22T22:23:29.332Z" }, + { url = "https://files.pythonhosted.org/packages/da/b5/b1d3c5f9d3fa5aeef74265f9c64de3c34a0d6d5cd3c81c8b17d5c8f10ed4/rpds_py-0.28.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d3fd16b6dc89c73a4da0b4ac8b12a7ecc75b2864b95c9e5afed8003cb50a728", size = 402410, upload-time = "2025-10-22T22:23:31.14Z" }, + { url = "https://files.pythonhosted.org/packages/74/ae/cab05ff08dfcc052afc73dcb38cbc765ffc86f94e966f3924cd17492293c/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6796079e5d24fdaba6d49bda28e2c47347e89834678f2bc2c1b4fc1489c0fb01", size = 553593, upload-time = "2025-10-22T22:23:32.834Z" }, + { url = "https://files.pythonhosted.org/packages/70/80/50d5706ea2a9bfc9e9c5f401d91879e7c790c619969369800cde202da214/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:76500820c2af232435cbe215e3324c75b950a027134e044423f59f5b9a1ba515", size = 576925, upload-time = "2025-10-22T22:23:34.47Z" }, + { url = "https://files.pythonhosted.org/packages/ab/12/85a57d7a5855a3b188d024b099fd09c90db55d32a03626d0ed16352413ff/rpds_py-0.28.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bbdc5640900a7dbf9dd707fe6388972f5bbd883633eb68b76591044cfe346f7e", size = 542444, upload-time = "2025-10-22T22:23:36.093Z" }, + { url = "https://files.pythonhosted.org/packages/6c/65/10643fb50179509150eb94d558e8837c57ca8b9adc04bd07b98e57b48f8c/rpds_py-0.28.0-cp314-cp314-win32.whl", hash = "sha256:adc8aa88486857d2b35d75f0640b949759f79dc105f50aa2c27816b2e0dd749f", size = 207968, upload-time = "2025-10-22T22:23:37.638Z" }, + { url = "https://files.pythonhosted.org/packages/b4/84/0c11fe4d9aaea784ff4652499e365963222481ac647bcd0251c88af646eb/rpds_py-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:66e6fa8e075b58946e76a78e69e1a124a21d9a48a5b4766d15ba5b06869d1fa1", size = 218876, upload-time = "2025-10-22T22:23:39.179Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/3ab3b86ded7bb18478392dc3e835f7b754cd446f62f3fc96f4fe2aca78f6/rpds_py-0.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6fe887c2c5c59413353b7c0caff25d0e566623501ccfff88957fa438a69377d", size = 212506, upload-time = "2025-10-22T22:23:40.755Z" }, + { url = "https://files.pythonhosted.org/packages/51/ec/d5681bb425226c3501eab50fc30e9d275de20c131869322c8a1729c7b61c/rpds_py-0.28.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7a69df082db13c7070f7b8b1f155fa9e687f1d6aefb7b0e3f7231653b79a067b", size = 355433, upload-time = "2025-10-22T22:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/568c5e689e1cfb1ea8b875cffea3649260955f677fdd7ddc6176902d04cd/rpds_py-0.28.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b1cde22f2c30ebb049a9e74c5374994157b9b70a16147d332f89c99c5960737a", size = 342601, upload-time = "2025-10-22T22:23:44.372Z" }, + { url = "https://files.pythonhosted.org/packages/32/fe/51ada84d1d2a1d9d8f2c902cfddd0133b4a5eb543196ab5161d1c07ed2ad/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5338742f6ba7a51012ea470bd4dc600a8c713c0c72adaa0977a1b1f4327d6592", size = 372039, upload-time = "2025-10-22T22:23:46.025Z" }, + { url = "https://files.pythonhosted.org/packages/07/c1/60144a2f2620abade1a78e0d91b298ac2d9b91bc08864493fa00451ef06e/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1460ebde1bcf6d496d80b191d854adedcc619f84ff17dc1c6d550f58c9efbba", size = 382407, upload-time = "2025-10-22T22:23:48.098Z" }, + { url = "https://files.pythonhosted.org/packages/45/ed/091a7bbdcf4038a60a461df50bc4c82a7ed6d5d5e27649aab61771c17585/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3eb248f2feba84c692579257a043a7699e28a77d86c77b032c1d9fbb3f0219c", size = 518172, upload-time = "2025-10-22T22:23:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/54/dd/02cc90c2fd9c2ef8016fd7813bfacd1c3a1325633ec8f244c47b449fc868/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3bbba5def70b16cd1c1d7255666aad3b290fbf8d0fe7f9f91abafb73611a91", size = 399020, upload-time = "2025-10-22T22:23:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/ab/81/5d98cc0329bbb911ccecd0b9e19fbf7f3a5de8094b4cda5e71013b2dd77e/rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3114f4db69ac5a1f32e7e4d1cbbe7c8f9cf8217f78e6e002cedf2d54c2a548ed", size = 377451, upload-time = "2025-10-22T22:23:53.711Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/4d5bcd49e3dfed2d38e2dcb49ab6615f2ceb9f89f5a372c46dbdebb4e028/rpds_py-0.28.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4b0cb8a906b1a0196b863d460c0222fb8ad0f34041568da5620f9799b83ccf0b", size = 390355, upload-time = "2025-10-22T22:23:55.299Z" }, + { url = "https://files.pythonhosted.org/packages/3f/79/9f14ba9010fee74e4f40bf578735cfcbb91d2e642ffd1abe429bb0b96364/rpds_py-0.28.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf681ac76a60b667106141e11a92a3330890257e6f559ca995fbb5265160b56e", size = 403146, upload-time = "2025-10-22T22:23:56.929Z" }, + { url = "https://files.pythonhosted.org/packages/39/4c/f08283a82ac141331a83a40652830edd3a4a92c34e07e2bbe00baaea2f5f/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1e8ee6413cfc677ce8898d9cde18cc3a60fc2ba756b0dec5b71eb6eb21c49fa1", size = 552656, upload-time = "2025-10-22T22:23:58.62Z" }, + { url = "https://files.pythonhosted.org/packages/61/47/d922fc0666f0dd8e40c33990d055f4cc6ecff6f502c2d01569dbed830f9b/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b3072b16904d0b5572a15eb9d31c1954e0d3227a585fc1351aa9878729099d6c", size = 576782, upload-time = "2025-10-22T22:24:00.312Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0c/5bafdd8ccf6aa9d3bfc630cfece457ff5b581af24f46a9f3590f790e3df2/rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b670c30fd87a6aec281c3c9896d3bae4b205fd75d79d06dc87c2503717e46092", size = 544671, upload-time = "2025-10-22T22:24:02.297Z" }, + { url = "https://files.pythonhosted.org/packages/2c/37/dcc5d8397caa924988693519069d0beea077a866128719351a4ad95e82fc/rpds_py-0.28.0-cp314-cp314t-win32.whl", hash = "sha256:8014045a15b4d2b3476f0a287fcc93d4f823472d7d1308d47884ecac9e612be3", size = 205749, upload-time = "2025-10-22T22:24:03.848Z" }, + { url = "https://files.pythonhosted.org/packages/d7/69/64d43b21a10d72b45939a28961216baeb721cc2a430f5f7c3bfa21659a53/rpds_py-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7a4e59c90d9c27c561eb3160323634a9ff50b04e4f7820600a2beb0ac90db578", size = 216233, upload-time = "2025-10-22T22:24:05.471Z" }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "webcolors" +version = "25.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] From cc2ddb4a3bd7751fe2f3e0830d504c9b5ab0d8f0 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Nov 2025 19:54:37 -0500 Subject: [PATCH 72/87] remove all mmap writing and default to no fsync (to match iavl/v1 behavior) --- analysis/analysis.ipynb | 935 +++++++++++++++++++++++++++++++++++++- analysis/analysis.py | 18 +- iavlx/branch_layout.go | 19 +- iavlx/changeset.go | 98 +--- iavlx/changeset_files.go | 58 ++- iavlx/changeset_info.go | 51 +++ iavlx/changeset_writer.go | 39 +- iavlx/cleanup.go | 16 +- iavlx/compactor.go | 46 +- iavlx/leaf_layout.go | 7 +- iavlx/mmap.go | 11 +- iavlx/node_id.go | 97 +--- iavlx/options.go | 27 +- iavlx/orphans.go | 56 +++ iavlx/reader.go | 4 - iavlx/tree_store.go | 10 +- iavlx/tree_test.go | 19 +- 17 files changed, 1179 insertions(+), 332 deletions(-) create mode 100644 iavlx/orphans.go diff --git a/analysis/analysis.ipynb b/analysis/analysis.ipynb index 3a899d9e8503..348ae187d486 100644 --- a/analysis/analysis.ipynb +++ b/analysis/analysis.ipynb @@ -6,8 +6,8 @@ "metadata": { "collapsed": true, "ExecuteTime": { - "end_time": "2025-11-04T19:06:32.346896Z", - "start_time": "2025-11-04T19:06:29.902440Z" + "end_time": "2025-11-04T22:30:42.717510Z", + "start_time": "2025-11-04T22:30:42.714513Z" } }, "source": [ @@ -16,26 +16,26 @@ "import pandas as pd" ], "outputs": [], - "execution_count": 1 + "execution_count": 17 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T19:06:33.304373Z", - "start_time": "2025-11-04T19:06:32.556778Z" + "end_time": "2025-11-04T22:30:43.898313Z", + "start_time": "2025-11-04T22:30:42.757981Z" } }, "cell_type": "code", "source": "runs = load_otel_runs(\"/Users/arc/iavl-bench-data/sims\")", "id": "1d7a28bf36057a9d", "outputs": [], - "execution_count": 2 + "execution_count": 18 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T19:06:35.205099Z", - "start_time": "2025-11-04T19:06:33.320602Z" + "end_time": "2025-11-04T22:30:46.442075Z", + "start_time": "2025-11-04T22:30:43.908082Z" } }, "cell_type": "code", @@ -49,10 +49,11 @@ { "data": { "text/plain": [ - " duration\n", - "run \n", - "iavl1 208.354362\n", - "iavlx 178.002386" + " duration\n", + "run \n", + "iavlx-no-fsync 161.325176\n", + "iavl1 208.354362\n", + "iavlx 178.002386" ], "text/html": [ "
\n", @@ -82,6 +83,10 @@ " \n", " \n", " \n", + " iavlx-no-fsync\n", + " 161.325176\n", + " \n", + " \n", " iavl1\n", " 208.354362\n", " \n", @@ -94,18 +99,18 @@ "
" ] }, - "execution_count": 3, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 3 + "execution_count": 19 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T19:06:39.439986Z", - "start_time": "2025-11-04T19:06:35.251031Z" + "end_time": "2025-11-04T22:30:47.996699Z", + "start_time": "2025-11-04T22:30:46.493102Z" } }, "cell_type": "code", @@ -116,6 +121,22 @@ "data": { "application/vnd.plotly.v1+json": { "data": [ + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx-no-fsync", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + }, + "y": { + "dtype": "f8", + "bdata": "cT0K1yP2n0CXbhKDwDOgQHNoke38P5NAZDvfT42emkDl0CLbOemiQBWuR+H6SplAyqFFtjMfoEBFtvP91NCKQKAaL90kzJZAa7x0kxiimEA6tMh2PkGSQOXQItt5FppATmIQWDn0NUDD9Shcj0JNQMDKoUW2Q0VAg8DKoUV2LkB9PzVeuqlBQClcj8L1GEFA/Knx0k3iPkBKDAIrhwZQQMuhRbbz/TRAEFg5tMhmQEAzMzMzMzNDQHe+nxovnUBAke18PzVeOkAhsHJokS02QDZeukkMQjtAa7x0kxjkOUAZBFYOLdI1QOomMQisfDdA30+Nl24yM0DD9Shcj4I8QHe+nxovvTNAbef7qfEyNECMbOf7qRExQBKDwMqhxTBAdZMYBFaOMkBYObTIdl5KQOXQItv5XkhAAAAAAABQQEBiEFg5tPhGQBbZzvdTw0tA5/up8dIdSEDC9Shcj3JaQN0kBoGV13lAAiuHFlnPkkCHFtnO952cQLKd76eGUqFAvp8aL91id0D8qfHSTU5+QEJg5dAiM2lAd76fGq+GmECzne+nxgWdQKAaL90kPZJA1XjpJjFAcUChRbbz/f+OQMdLN4lBxHFAHoXrUbjojEAcWmQ7349yQIPAyqFF7IFAdZMYBNaAmECe76fGS39YQFyPwvUoko5AVOOlm8RASUDhehSuR7yAQDVeukkMLppAxCCwcmiahkCBlUOLrCamQKJFtvP9yJxAiBbZzvfSiECJQWDlUCOTQJ7vp8ZLpZFARbbz/dToTUB/arx0k7g1QEoMAiuHNj1AlBgEVg4tNUDo+6nx0u02QFTjpZvEMEFAHVpkO99PNECe76fGSwdEQMdLN4lBADxAAAAAAABAN0Aj2/l+alw6QLKd76fGO0ZA2c73U+MVSkAX2c73U/NFQPLSTWIQ+ERA7Xw/NV7aP0AlBoGVQ0tJQN9PjZdukkxAEFg5tMgWRECyne+nxus6QC/dJAaB9T9AlkOLbOfbM0BI4XoUrodAQOtRuB6F20VAqMZLN4khRUB1kxgEVr5AQClcj8L12EFAK4cW2c7XMUCwcmiR7Zw4QKabxCCw0kZAAAAAAABANkAlBoGVQ9tNQPCnxks38VlA+n5qvHTDRUDsUbgehYtEQAAAAAAA4FhAEoPAyqGFMUApXI/C9egxQGmR7Xw/tTFAsp3vp8YLMUAdWmQ73+8wQB1aZDvfrzVAbOf7qfFSMUB9PzVeukkxQJ3vp8ZL9zRAf2q8dJPYMUDNzMzMzKwxQFtkO99PTTFAEFg5tMg2MUDhehSuR2ExQArXo3A9ijRAppvEILDyMEDl0CLb+V4wQGDl0CLbOTBAlUOLbOebMUAPLbKd70edQDMzMzMz541AcT0K16OgaEBMN4lB4JaTQEfhehSuy41AyXa+nxoUkEAbL90kBvd9QHe+nxovCWpAZDvfT43yj0BfukkMAhGaQMZLN4nBJ5BAmpmZmZl6gECq8dJNYix6QAvXo3A9yHlAEFg5tMgWSECQwvUo3CCUQArXo3A9JHpA7FG4HoVlnUCHFtnO98adQGiR7Xx/7qRAm8QgsHKYa0CBlUOLbKiTQKJFtvN9zKdAXI/C9SjJo0Cq8dJN4jOtQDEIrBx6vbJAeekmMQhVpEBeukkMglqpQH9qvHSTdoxA2/l+arzkXEAtsp3vp1RxQG4Sg8BKlJ9AgZVDi2w5mkDMzMzMzD+LQLKd76cGnKtAd76fGi/om0AOLbKdryGlQI/C9Sjct5ZAeekmMUjXokAnMQisXCiuQLgehesRAapA9ihcj8K/cUCDwMqhxf2aQHa+nxrv6qJAR+F6FK5mjEARWDm0yIiWQAAAAAAA+JFAPN9PjZfisUAfhetRuMuCQBKDwMqBebFAAAAAAIATm0CXbhKDQFqkQN9PjZfu7qVA9P3UeOmPr0DXo3A9SmumQNEi2/l+BHJAXI/C9ah0lECTGARWzgShQHJoke18lX9A001iEFgElkAMAiuHFo2OQF66SQyCC6BADy2yne+7ekDpJjEIrFBsQHSTGARW/khAZ2ZmZmZWdEDy0k1iEFhMQDMzMzMzY0NAcD0K16MgQ0BSuB6F6xE3QBKDwMqhJUhABFYOLbItRECkcD0K11tTQD81XrpJPEdAItv5fmoqdUCuR+F6FDplQMZLN4lBgDVA16NwPQq/VECPwvUoXEdVQAmsHFpkGzJAg8DKoUXWRUDZzvdT4wVJQEa28/3UyEFAcT0K16MgQUCF61G4HsU6QB+F61G4vkxAIbByaJEtOECVQ4ts59tQQLByaJHtPEpA+X5qvHRDUkAzMzMzM5NIQNnO91PjfVFAnu+nxkv3RkBrvHSTGEQ9QH9qvHSTSEBAiUFg5dCSQ0CLbOf7qbEwQCuHFtnON0BA30+Nl25CR0BT46WbxOA9QN9PjZduMjFAyHa+nxqvOUAv3SQGgfUxQL10kxgE9jFAGQRWDi1SMkCuR+F6FM4xQBKDwMqhpTFAKVyPwvWIMUCoxks3ieExQKAaL90k5jFAAiuHFtmuMUDpJjEIrBwyQH0/NV66yTFAGi/dJAahMUC6SQwCKycyQH9qvHSTGDFAF9nO91PjMUBs5/up8ZI4QN0kBoGV4zFAF9nO91PjMUCXbhKDwKoxQOOlm8QgkDFAEoPAyqFlMUA=" + }, + "type": "scatter" + }, { "line": { "width": 2 @@ -950,7 +971,887 @@ "output_type": "display_data" } ], - "execution_count": 4 + "execution_count": 20 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-04T22:30:49.462496Z", + "start_time": "2025-11-04T22:30:48.036322Z" + } + }, + "cell_type": "code", + "source": "plot_block_durations(runs, span_name='Commit')", + "id": "88882244feb963c9", + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "data": [ + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx-no-fsync", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + }, + "y": { + "dtype": "f8", + "bdata": "16NwPQpXIEBqvHSTGEQjQEA1XrpJjCRAF9nO91NjIUBYObTIdn4lQCuHFtnONyJA8tJNYhCYJECYbhKDwMoaQDvfT42XbiFAEoPAyqHFIUDn+6nx0g0gQFpkO99PjSNA7nw/NV66BEDfT42XbhLxP5ZDi2zn++0/F9nO91Pj5T9OYhBYObTsP4/C9Shcj+o/qvHSTWIQ6D8rhxbZzvfrP3e+nxov3eg/g8DKoUW26z9aZDvfT43rP6rx0k1iEPI/xks3iUFg8T8X2c73U+PtP9Ei2/l+avw/tvP91Hjp7j9mZmZmZmbqP46XbhKDwOo/DAIrhxbZ6j9zaJHtfD/zP+kmMQisHOo/j8L1KFyP6j8nMQisHFroP46XbhKDwOY/XI/C9Shc+z9ANV66SQzqP2ZmZmZmZuo/fT81XrpJ6D89CtejcD3qPxgEVg4tsgBAGQRWDi2y8T9Di2zn+6npPx1aZDvfzx1AVOOlm8SgGkDGSzeJQaAgQFK4HoXr0SBA001iEFg5DEBvEoPAyqEQQHE9CtejcARA7nw/NV46HkCoxks3iQEpQDQzMzMzsxpAxks3iUFgDEC28/3UeOkTQBbZzvdT4wpA9ihcj8J1GEBEi2zn+6kDQE5iEFg5tBNAL90kBoEVHECwcmiR7XwAQD0K16NwPRlAUI2XbhKD/j/qJjEIrBwQQEFg5dAi2yBAGARWDi2yFEB/arx0kxgsQJ8aL90kBiJA001iEFi5FECsHFpkO98ZQJVDi2zn+xhAzczMzMzMCUBqvHSTGATwPy/dJAaBle8/XrpJDAIr8T+yne+nxkvxPwvXo3A9Cu8/PzVeukkM7j/azvdT46XvP2ZmZmZmZu4/QmDl0CLb7T/dJAaBlUPrP0w3iUFg5fA/TmIQWDm0/D/RItv5fmr0P/yp8dJNYvA/xks3iUFg9T+ZmZmZmZnzP1pkO99PjfM/HVpkO99P8T8fhetRuB7zP90kBoGVQwxA5dAi2/l+8D8j2/l+arzwPwisHFpkO/E/nMQgsHJo8T8Sg8DKoUXwPwisHFpkO/U/PzVeukkM7j+d76fGSzfxP1+6SQwCK+8/TWIQWDm08j/jpZvEILDuP9NNYhBYOf4/+n5qvHSTAED+1HjpJjHwP7x0kxgEVvA/SQwCK4cW8T+6SQwCK4fuP5qZmZmZme0/xks3iUFg7T9Di2zn+6npP4ts5/up8e4/PzVeukkM7j9uEoPAyqHtP0W28/3U+BJAKVyPwvUo8j/6fmq8dJPwP6abxCCwcvA/aJHtfD817j8pXI/C9SjwPx1aZDvfT/E/v58aL90k6j+8dJMYBFbqPxBYObTIdvA/6SYxCKwc7j9qvHSTGIQjQDvfT42XbhhAmpmZmZmZBEBwPQrXo/AeQMdLN4lB4BtA5dAi2/l+GkDkpZvEILAQQMl2vp8aLwhADy2yne8nFUD6fmq8dJMiQIGVQ4tsZxlAbxKDwMqhDEDHSzeJQWAKQHE9CtejcApAAiuHFtnO+z8PLbKd7ycdQC2yne+nRhFAf2q8dJOYIkBg5dAi23kiQOJ6FK5HoSZAv58aL90kBkAdWmQ7388ZQDiJQWDlEClAQmDl0CLbJUBANV66SUwsQIcW2c738zFAlBgEVg5tJkD6fmq8dFMrQBBYObTI9hdAMQisHFpkAkDNzMzMzAwjQEOLbOf7qS1AJQaBlUMLJEDJdr6fGi8UQHnpJjEIbC5A/dR46SaxH0Dn+6nx0k0pQO+nxks3SShAVOOlm8RgJkCuR+F6FO4wQN0kBoGVwy1AvXSTGARWBkDufD81XnoiQHE9CtejMClA61G4HoVrFkB1kxgEVg4cQCUGgZVDCxpAOrTIdr5fMkA4iUFg5VARQPP91HjpZjNAHVpkO98PMEAfhetRuJ4oQJ8aL90kRipAOrTIdr5fMUAaL90kBkEpQH0/NV66SQ1AF9nO91PjJEAlBoGVQxtBQAwCK4cWmSJADAIrhxaZIUBKDAIrh5YZQHA9CtejEDBAg8DKoUW2AkD2KFyPwvX0P39qvHSTGPg/5KWbxCCgREAbL90kBoHzP7bz/dR46fA/Di2yne+n8j/GSzeJQWD1PwaBlUOLbPM/PzVeukkM9D8MAiuHFtnyP30/NV66SfA/qvHSTWIQ/D9CYOXQItv1P6rx0k1iEPQ/BoGVQ4ts9T9eukkMAiv1P7x0kxgEVvA/SOF6FK5H9T/8qfHSTeITQA4tsp3vp/I/QDVeukkM8j+gGi/dJAbxPyCwcmiR7fA/eekmMQisAkD6fmq8dJPwP2iR7Xw/NfA/rkfhehSu8T+4HoXrUbjyPyGwcmiR7QdAyqFFtvP98j9iEFg5tMjwPyyHFtnO9+8/AAAAAAAA8D+CwMqhRbbvP++nxks3ifE/46WbxCCw8j9kO99PjZfwP/p+arx0k/Q/Dy2yne+n8D/n+6nx0k3wP+F6FK5H4fA/WDm0yHa+7z8tsp3vp8bxP6jGSzeJQfA/PgrXo3A98D89CtejcD3uP7pJDAIrh+4/XI/C9Shc8T+wcmiR7XzxP9ijcD0K1/E/f2q8dJMY8j8PLbKd76fwPx1aZDvfT+0/wMqhRbbz8T9oke18PzXyPylcj8L1KPA/arx0kxgE8D/eJAaBlUPxP1K4HoXrUfA/AAAAAAAA8D8=" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavl1", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + }, + "y": { + "dtype": "f8", + "bdata": "8dJNYhBYHUBxPQrXo3BGQF66SQwCqxxA9P3UeOkmIkDjpZvEIPApQBxaZDvfDyNAd76fGi+dJ0DdJAaBlUMYQHNoke18vx5AeekmMQhsIUA9CtejcL0fQL+fGi/dJCtAVOOlm8Qg7D/+1HjpJrlXQLKd76fGK01AZmZmZmZm6j/O91PjpbtPQPT91HjpJl1AKVyPwvXoQUD4U+Olm1RKQB6F61G4TldAQ4ts5/up7T8ZBFYOLbLtP2Q730+Nh1VAgpVDi2zn7z+F61G4HoXvP1g5tMh2Xk5AGy/dJAb1YEDufD81Xrr1P57vp8ZLl0RA6SYxCKwc6j8lBoGVQ+NfQAaBlUOLbOs/qMZLN4k5UkA5tMh2vp/4P4ts5/up8e4/rBxaZDuXVkBMN4lBYCU1QAisHFpkO+8/gZVDi2x3RUBI4XoUrvdBQOtRuB6F6+0/UI2XbhIjPUDiehSuR+HuP1g5tMh2PhBAsHJoke38GUB/arx0k9ghQFK4HoXrkSBAppvEILByB0AVrkfhepQQQHJoke18PwhAYOXQItt5IUBvEoPAymEgQMuhRbbzzUVA16NwPQrXB0Aj2/l+ajwTQMDKoUW2o0xADQIrhxbZE0AMAiuHFtkJQIlBYOXQohJARrbz/dR4IUDD9Shcj8IAQClcj8L1EFpACKwcWmQ7+z9YObTIdj4SQB6F61G4XiRAAiuHFtnOF0C28/3UeGktQJvEILBy6CBAwcqhRbZzFEAnMQisHMJXQG4Sg8DKIRpACKwcWmT7XEBSuB6F61H0P0fhehSuR/c/PgrXo3A99D/iehSuR+HyPyGwcmiRbTBAUrgehethV0DVeOkmMQj6P4GVQ4ts5/c/rBxaZDvfQkDl0CLb+VpiQBFYObTIflJA7nw/NV668T+Nl24Sg8DwP1TjpZvEQDFA4XoUrkfhAEDhehSuR9FBQLx0kxgEVvQ/YeXQItv58D+uR+F6FM5jQLpJDAIrh/I/g8DKoUUOUUBKDAIrhxbzP+XQItv5fjNAy6FFtvOVYEAgsHJoke3wP/T91HjpxkFAF9nO91MzXUA/NV66SQzwP4PAyqFFtvU/HVpkO98XUEAmBoGVQ0tPQPhT46WbxPA/eekmMQi0YUC+nxov3STwP1YOLbKd7+8/j8L1KFyP8D9/arx0kxjwP7gehetRwFFAE4PAyqFF7j8AAAAAACBPQEA1XrpJDAJAIbByaJENQkDRItv5fmrwP39qvHSTGPA/JQaBlUPLMEBeukkMAivxP2Dl0CLb+e4/5/up8dJN8D9mZmZmZk5SQMdLN4lBwDpAN4lBYOXQ7j9kO99PjZfwP5DC9ShcX0RAeekmMQis8D8hsHJoka0mQN9PjZduEh9ARIts5/upA0ApXI/C9SgcQGZmZmZmZh5Avp8aL92kGUAAAAAAAAAPQGIQWDm0yARARbbz/dT4FUBg5dAi2/keQOF6FK5HISBAFtnO91NjEEDdJAaBlUMKQFyPwvUoXAlA6iYxCKx8YEAX2c73U+tdQPYoXI/C9QlAhxbZzvcTZkDy0k1iEBglQBWuR+F6lChAy6FFtvP9DECYbhKDwMoeQNEi2/l+qjtAnMQgsHLoJUAPLbKd76csQLKd76fGKzJAcD0K16PwJ0DgT42XbqJPQJ8aL90khhZAdJMYBFYOAkATg8DKoUUPQAMrhxbZblpAwMqhRbZzIkDEILByaJEXQPP91HjpZjZAvXSTGARWIkDsUbgehSsoQGHl0CLbeSBA/Knx0k2iJ0Dvp8ZLNykxQC2yne+nBi1AEoPAyqFFCUDNzMzMzEwiQDeJQWDlAF5AuB6F61FYRUAX2c73U+MdQFyPwvUoXCVALbKd76eeUUCBlUOLbGcXQNnO91Pj5VhAI9v5fmp8JEC9dJMYBBYqQFg5tMh2fitA2/l+arw0MUCYbhKDwAowQOF6FK5H4QRAzczMzMxMGUAX2c73U+MhQCPb+X5qvBBAyXa+nxpvU0Cq8dJNYlBeQMuhRbbzPSJArBxaZDvfAkApXI/C9Sj6P9Ei2/l+avA/z/dT46Wb8j+28/3UeBlQQFG4HoXr+WBAy6FFtvMdOECsHFpkO9/xPwmsHFpko1lADi2yne+n8j9nZmZmZmb0Py/dJAaBFU1Af2q8dJMY/j/jpZvEILD2P8uhRbbz/fA/FtnO91Pj9z9g5dAi2/lDQC2yne+nhjhAFa5H4XoESUA6tMh2vg9CQNv5fmq8tDZAGARWDi0WY0B7FK5H4XoKQEW28/3UWFZAPQrXo3BNSUApXI/C9SgtQArXo3A92kFArBxaZDsfLUCVQ4ts5/vzPwwCK4cW2fQ/arx0kxiUY0CDwMqhRXZSQHsUrkfhejlAVOOlm8Qg9D/wp8ZLNxFXQK5H4XoUnk5AbOf7qfHS8T8730+NlwZQQPp+arx0c0VAwvUoXI/C8z8fhetRuK5EQC2yne+nxu8/c2iR7XwvUUCQwvUoXC80QBKDwMqhRfI/3SQGgZVD7z9H4XoUrgdQQFTjpZvE0FBA0SLb+X5q8D+amZmZmelFQOXQItv5fvA/bxKDwMqRUEBI4XoUrkfxP5QYBFYOzUxAqvHSTWIQ9D9g5dAi25lSQAwCK4cW2fI/SgwCK4cWNkArhxbZzvfxP9NNYhBYIVNAZTvfT42X8j8=" + }, + "type": "scatter" + } + ], + "layout": { + "template": { + "data": { + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scattermap": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermap" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "#C8D4E3" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "baxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Commit Duration Comparison" + }, + "xaxis": { + "title": { + "text": "Block Number" + } + }, + "yaxis": { + "title": { + "text": "Duration (ms)" + } + }, + "hovermode": "x unified" + }, + "config": { + "plotlyServerURL": "https://plot.ly" + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 21 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-04T22:30:49.479104Z", + "start_time": "2025-11-04T22:30:49.476589Z" + } + }, + "cell_type": "code", + "source": "", + "id": "8922bfaae8e7ed45", + "outputs": [], + "execution_count": null } ], "metadata": { diff --git a/analysis/analysis.py b/analysis/analysis.py index 8a9cac903cae..9d8bf55da472 100644 --- a/analysis/analysis.py +++ b/analysis/analysis.py @@ -34,7 +34,7 @@ def block_summary(con: duckdb.DuckDBPyConnection) -> BlockSummary: - block_count: Total number of blocks processed Example: - >>> from read_otel import load_otel_data + >>> from analysis.read_otel import load_otel_data >>> con = load_otel_data('/path/to/data') >>> summary = block_summary(con) >>> print(f"Processed {summary.block_count} blocks in {summary.total_duration_seconds:.2f}s") @@ -57,7 +57,7 @@ def block_summary(con: duckdb.DuckDBPyConnection) -> BlockSummary: ) -def block_durations(con: duckdb.DuckDBPyConnection) -> pd.DataFrame: +def block_durations(con: duckdb.DuckDBPyConnection, span_name: str = 'Block') -> pd.DataFrame: """ Get duration for each block. @@ -70,7 +70,7 @@ def block_durations(con: duckdb.DuckDBPyConnection) -> pd.DataFrame: - duration_ms: Block duration in milliseconds Example: - >>> from read_otel import load_otel_data + >>> from analysis.read_otel import load_otel_data >>> con = load_otel_data('/path/to/data') >>> df = block_durations(con) >>> df.head() @@ -80,12 +80,12 @@ def block_durations(con: duckdb.DuckDBPyConnection) -> pd.DataFrame: ROW_NUMBER() OVER (ORDER BY start_time) AS block_number, EXTRACT(EPOCH FROM duration) * 1000 AS duration_ms FROM spans - WHERE span_name = 'Block' AND scope = 'cosmos-sdk/baseapp' + WHERE span_name = ? AND scope = 'cosmos-sdk/baseapp' ORDER BY start_time - """).df() + """, params=[span_name]).df() -def plot_block_durations(runs: Runs) -> go.Figure: +def plot_block_durations(runs: Runs, span_name: str = 'Block') -> go.Figure: """ Create a plotly line chart comparing block durations across runs. @@ -96,7 +96,7 @@ def plot_block_durations(runs: Runs) -> go.Figure: Plotly Figure object with block duration traces for each run Example: - >>> from read_otel import load_otel_runs + >>> from analysis.read_otel import load_otel_runs >>> runs = load_otel_runs('/path/to/data') >>> fig = plot_block_durations(runs) >>> fig.show() @@ -104,7 +104,7 @@ def plot_block_durations(runs: Runs) -> go.Figure: fig = go.Figure() for run_name, con in runs.items(): - df = block_durations(con) + df = block_durations(con, span_name) fig.add_trace(go.Scatter( x=df['block_number'], y=df['duration_ms'], @@ -114,7 +114,7 @@ def plot_block_durations(runs: Runs) -> go.Figure: )) fig.update_layout( - title='Block Duration Comparison', + title=f'{span_name} Duration Comparison', xaxis_title='Block Number', yaxis_title='Duration (ms)', hovermode='x unified', diff --git a/iavlx/branch_layout.go b/iavlx/branch_layout.go index fdc45815dece..4c2c2f0e15fc 100644 --- a/iavlx/branch_layout.go +++ b/iavlx/branch_layout.go @@ -16,16 +16,15 @@ const ( ) type BranchLayout struct { - Id NodeID - Left NodeID - Right NodeID - LeftOffset uint32 // absolute offset - RightOffset uint32 // absolute offset - KeyOffset uint32 - Height uint8 - Size uint32 // TODO 5 bytes? - OrphanVersion uint32 // TODO 5 bytes? - Hash [32]byte + Id NodeID + Left NodeID + Right NodeID + LeftOffset uint32 // absolute offset + RightOffset uint32 // absolute offset + KeyOffset uint32 + Height uint8 + Size uint32 // TODO 5 bytes? (there are 3 bytes of padding here) + Hash [32]byte } func (b BranchLayout) ID() NodeID { diff --git a/iavlx/changeset.go b/iavlx/changeset.go index 2a1101370905..c071cb6e7b1f 100644 --- a/iavlx/changeset.go +++ b/iavlx/changeset.go @@ -9,15 +9,15 @@ import ( type Changeset struct { files *ChangesetFiles + info *ChangesetInfo // we copy this so that it can be accessed even in shared changesets treeStore *TreeStore - info *ChangesetInfo - infoMmap *StructMmap[ChangesetInfo] kvLog *KVLog // TODO make sure we handle compaction here too branchesData *NodeMmap[BranchLayout] leavesData *NodeMmap[LeafLayout] versionsData *StructMmap[VersionInfo] + orphanWriter *OrphanWriter refCount atomic.Int32 evicted atomic.Bool @@ -65,18 +65,19 @@ func (cr *Changeset) InitShared(files *ChangesetFiles) error { return fmt.Errorf("failed to open versions data file: %w", err) } - // we need a reference to the changeset info mmap to be able to flush it later when orphans are marked + cr.orphanWriter = NewOrphanWriter(files.orphansFile) + cr.info = files.info - cr.infoMmap = files.infoMmap return nil } func (cr *Changeset) getVersionInfo(version uint32) (*VersionInfo, error) { - if version < cr.info.StartVersion || version >= cr.info.StartVersion+uint32(cr.versionsData.Count()) { - return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, cr.info.StartVersion, cr.info.StartVersion+uint32(cr.versionsData.Count())-1) + info := cr.info + if version < info.StartVersion || version >= info.StartVersion+uint32(cr.versionsData.Count()) { + return nil, fmt.Errorf("version %d out of range for changeset (have %d..%d)", version, info.StartVersion, info.StartVersion+uint32(cr.versionsData.Count())-1) } - return cr.versionsData.UnsafeItem(version - cr.info.StartVersion), nil + return cr.versionsData.UnsafeItem(version - info.StartVersion), nil } func (cr *Changeset) ReadK(nodeId NodeID, offset uint32) (key []byte, err error) { @@ -231,62 +232,39 @@ func (cr *Changeset) Resolve(nodeId NodeID, fileIdx uint32) (Node, error) { var ErrDisposed = errors.New("changeset disposed") func (cr *Changeset) MarkOrphan(version uint32, nodeId NodeID) error { - if cr.evicted.Load() { - return ErrDisposed - } - cr.Pin() - defer cr.Unpin() - - nodeVersion := uint32(nodeId.Version()) - vi, err := cr.getVersionInfo(nodeVersion) + err := cr.orphanWriter.WriteOrphan(version, nodeId) if err != nil { - return err + return fmt.Errorf("failed to write orphan node: %w", err) } + info := cr.info if nodeId.IsLeaf() { - leaf, err := cr.leavesData.FindByID(nodeId, &vi.Leaves) - if err != nil { - return err - } - - if leaf.OrphanVersion == 0 { - leaf.OrphanVersion = version - cr.info.LeafOrphans++ - cr.info.LeafOrphanVersionTotal += uint64(version) - cr.dirtyLeaves.Store(true) - } + info.LeafOrphans++ + info.LeafOrphanVersionTotal += uint64(version) } else { - branch, err := cr.branchesData.FindByID(nodeId, &vi.Branches) - if err != nil { - return err - } - - if branch.OrphanVersion == 0 { - branch.OrphanVersion = version - cr.info.BranchOrphans++ - cr.info.BranchOrphanVersionTotal += uint64(version) - cr.dirtyBranches.Store(true) - } + info.BranchOrphans++ + info.BranchOrphanVersionTotal += uint64(version) } return nil } func (cr *Changeset) ReadyToCompact(orphanPercentTarget float64, orphanAgeTarget uint32) bool { - leafOrphanCount := cr.info.LeafOrphans + info := cr.info + leafOrphanCount := info.LeafOrphans if leafOrphanCount > 0 { leafOrphanPercent := float64(leafOrphanCount) / float64(cr.leavesData.Count()) - leafOrphanAge := uint32(cr.info.LeafOrphanVersionTotal / uint64(cr.info.LeafOrphans)) + leafOrphanAge := uint32(info.LeafOrphanVersionTotal / uint64(info.LeafOrphans)) if leafOrphanPercent >= orphanPercentTarget && leafOrphanAge <= orphanAgeTarget { return true } } - branchOrphanCount := cr.info.BranchOrphans + branchOrphanCount := info.BranchOrphans if branchOrphanCount > 0 { branchOrphanPercent := float64(branchOrphanCount) / float64(cr.branchesData.Count()) - branchOrphanAge := uint32(cr.info.BranchOrphanVersionTotal / uint64(cr.info.BranchOrphans)) + branchOrphanAge := uint32(info.BranchOrphanVersionTotal / uint64(info.BranchOrphans)) if branchOrphanPercent >= orphanPercentTarget && branchOrphanAge <= orphanAgeTarget { return true } @@ -295,42 +273,13 @@ func (cr *Changeset) ReadyToCompact(orphanPercentTarget float64, orphanAgeTarget return false } -func (cr *Changeset) FlushOrphans() error { - cr.Pin() - defer cr.Unpin() - - wasDirty := false - if cr.dirtyLeaves.Load() { - wasDirty = true - err := cr.leavesData.Flush() - if err != nil { - return fmt.Errorf("failed to flush leaf data: %w", err) - } - cr.dirtyLeaves.Store(false) - } - if cr.dirtyBranches.Load() { - wasDirty = true - err := cr.branchesData.Flush() - if err != nil { - return fmt.Errorf("failed to flush branch data: %w", err) - } - cr.dirtyBranches.Store(false) - } - if wasDirty { - err := cr.infoMmap.Flush() - if err != nil { - return fmt.Errorf("failed to flush changeset info: %w", err) - } - } - return nil -} - func (cr *Changeset) Close() error { errs := []error{ cr.kvLog.Close(), cr.leavesData.Close(), cr.branchesData.Close(), cr.versionsData.Close(), + cr.orphanWriter.Flush(), } if cr.files != nil { errs = append(errs, cr.files.Close()) @@ -361,8 +310,6 @@ func (cr *Changeset) TryDispose() bool { cr.branchesData = nil cr.leavesData = nil cr.kvLog = nil - cr.info = nil - cr.infoMmap = nil // DO NOT set treeStore to nil, as deposed changesets should still forward calls to the main tree store // DO NOT set files to nil, as we might need to delete them later return true @@ -379,7 +326,8 @@ func (cr *Changeset) TotalBytes() int { } func (cr *Changeset) HasOrphans() bool { - return cr.info.LeafOrphans > 0 || cr.info.BranchOrphans > 0 + info := cr.info + return info.LeafOrphans > 0 || info.BranchOrphans > 0 } func (cr *Changeset) ResolveRoot(version uint32) (*NodePointer, error) { diff --git a/iavlx/changeset_files.go b/iavlx/changeset_files.go index 7f79dfabb410..36d84dfd2607 100644 --- a/iavlx/changeset_files.go +++ b/iavlx/changeset_files.go @@ -17,10 +17,9 @@ type ChangesetFiles struct { branchesFile *os.File leavesFile *os.File versionsFile *os.File + orphansFile *os.File infoFile *os.File - - info *ChangesetInfo - infoMmap *StructMmap[ChangesetInfo] + info *ChangesetInfo closed bool } @@ -70,37 +69,21 @@ func OpenChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogP return nil, fmt.Errorf("failed to create versions data file: %w", err) } - infoPath := filepath.Join(dir, "info.dat") - infoFile, err := os.OpenFile(infoPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + orphansPath := filepath.Join(dir, "orphans.dat") + orphansFile, err := os.OpenFile(orphansPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) if err != nil { - return nil, fmt.Errorf("failed to create changeset info file: %w", err) + return nil, fmt.Errorf("failed to create orphans data file: %w", err) } - // check file size to see if we need to initialize - stat, err := infoFile.Stat() + infoPath := filepath.Join(dir, "info.dat") + infoFile, err := os.OpenFile(infoPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) if err != nil { - return nil, fmt.Errorf("failed to stat info file: %w", err) - } - - if stat.Size() == 0 { - // file is empty, initialize it - infoWriter := NewStructWriter[ChangesetInfo](infoFile) - if err := infoWriter.Append(&ChangesetInfo{}); err != nil { - return nil, fmt.Errorf("failed to write initial changeset info: %w", err) - } - if err := infoWriter.Flush(); err != nil { - return nil, fmt.Errorf("failed to flush initial changeset info: %w", err) - } + return nil, fmt.Errorf("failed to create changeset info file: %w", err) } - // now create the mmap reader - infoMmap, err := NewStructReader[ChangesetInfo](infoFile) + info, err := ReadChangesetInfo(infoFile) if err != nil { - return nil, fmt.Errorf("failed to open changeset info: %w", err) - } - - if infoMmap.Count() != 1 { - return nil, fmt.Errorf("changeset info file has unexpected item count: %d", infoMmap.Count()) + return nil, fmt.Errorf("failed to read changeset info: %w", err) } return &ChangesetFiles{ @@ -112,9 +95,9 @@ func OpenChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogP branchesFile: branchesFile, leavesFile: leavesFile, versionsFile: versionsFile, + orphansFile: orphansFile, infoFile: infoFile, - info: infoMmap.UnsafeItem(0), - infoMmap: infoMmap, + info: info, }, nil } @@ -134,6 +117,14 @@ func (cr *ChangesetFiles) CompactedAtVersion() uint32 { return cr.compactedAt } +func (cr *ChangesetFiles) Info() *ChangesetInfo { + return cr.info +} + +func (cr *ChangesetFiles) RewriteInfo() error { + return RewriteChangesetInfo(cr.infoFile, cr.info) +} + type ChangesetDeleteArgs struct { SaveKVLogPath string } @@ -144,15 +135,17 @@ func (cr *ChangesetFiles) Close() error { } cr.closed = true - cr.info = nil - return errors.Join( + err := errors.Join( + cr.RewriteInfo(), cr.kvlogFile.Close(), cr.branchesFile.Close(), cr.leavesFile.Close(), cr.versionsFile.Close(), + cr.orphansFile.Close(), cr.infoFile.Close(), - cr.infoMmap.Close(), ) + cr.info = nil + return err } func (cr *ChangesetFiles) DeleteFiles(args ChangesetDeleteArgs) error { @@ -161,6 +154,7 @@ func (cr *ChangesetFiles) DeleteFiles(args ChangesetDeleteArgs) error { os.Remove(cr.leavesFile.Name()), os.Remove(cr.branchesFile.Name()), os.Remove(cr.versionsFile.Name()), + os.Remove(cr.orphansFile.Name()), } if cr.kvlogFile.Name() != args.SaveKVLogPath { errs = append(errs, os.Remove(cr.kvlogFile.Name())) diff --git a/iavlx/changeset_info.go b/iavlx/changeset_info.go index 45bec4e08dd7..34b21040b68e 100644 --- a/iavlx/changeset_info.go +++ b/iavlx/changeset_info.go @@ -1,5 +1,12 @@ package iavlx +import ( + "fmt" + "io" + "os" + "unsafe" +) + type ChangesetInfo struct { StartVersion uint32 EndVersion uint32 @@ -8,3 +15,47 @@ type ChangesetInfo struct { LeafOrphanVersionTotal uint64 BranchOrphanVersionTotal uint64 } + +// RewriteChangesetInfo truncates and rewrites the info file with the given changeset info. +func RewriteChangesetInfo(file *os.File, info *ChangesetInfo) error { + if err := file.Truncate(0); err != nil { + return fmt.Errorf("failed to truncate info file: %w", err) + } + if _, err := file.Seek(0, 0); err != nil { + return fmt.Errorf("failed to seek info file: %w", err) + } + + size := int(unsafe.Sizeof(*info)) + data := unsafe.Slice((*byte)(unsafe.Pointer(info)), size) + if _, err := file.Write(data); err != nil { + return fmt.Errorf("failed to write changeset info: %w", err) + } + + return nil +} + +// ReadChangesetInfo reads changeset info from a file. Returns an empty default struct if file is zero length. +func ReadChangesetInfo(file *os.File) (*ChangesetInfo, error) { + stat, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("failed to stat info file: %w", err) + } + + if stat.Size() == 0 { + return &ChangesetInfo{}, nil + } + + var info ChangesetInfo + size := int(unsafe.Sizeof(info)) + + if stat.Size() != int64(size) { + return nil, fmt.Errorf("info file has unexpected size: %d, expected %d", stat.Size(), size) + } + + buf := make([]byte, size) + if _, err := io.ReadFull(file, buf); err != nil { + return nil, fmt.Errorf("failed to read changeset info: %w", err) + } + + return (*ChangesetInfo)(unsafe.Pointer(&buf[0])), nil +} diff --git a/iavlx/changeset_writer.go b/iavlx/changeset_writer.go index 452f675106c0..7f7a53f1f226 100644 --- a/iavlx/changeset_writer.go +++ b/iavlx/changeset_writer.go @@ -117,7 +117,7 @@ func (cs *ChangesetWriter) CreatedSharedReader() (*Changeset, error) { func (cs *ChangesetWriter) Flush() error { return errors.Join( - cs.files.infoMmap.Flush(), + cs.files.RewriteInfo(), cs.leavesData.Flush(), cs.branchesData.Flush(), cs.kvlog.Flush(), @@ -177,15 +177,14 @@ func (cs *ChangesetWriter) writeBranch(np *NodePointer, node *MemNode) error { } layout := BranchLayout{ - Id: np.id, - Left: node.left.id, - Right: node.right.id, - LeftOffset: leftOffset, - RightOffset: rightOffset, - KeyOffset: keyOffset, - Height: node.height, - Size: uint32(node.size), // TODO check overflow - OrphanVersion: 0, + Id: np.id, + Left: node.left.id, + Right: node.right.id, + LeftOffset: leftOffset, + RightOffset: rightOffset, + KeyOffset: keyOffset, + Height: node.height, + Size: uint32(node.size), // TODO check overflow } copy(layout.Hash[:], node.hash) // TODO check length @@ -211,9 +210,8 @@ func (cs *ChangesetWriter) writeLeaf(np *NodePointer, node *MemNode) error { } layout := LeafLayout{ - Id: np.id, - KeyOffset: keyOffset, - OrphanVersion: 0, + Id: np.id, + KeyOffset: keyOffset, } copy(layout.Hash[:], node.hash) // TODO check length @@ -230,21 +228,6 @@ func (cs *ChangesetWriter) writeLeaf(np *NodePointer, node *MemNode) error { return nil } -func (cs *ChangesetWriter) createNodeRef(parentIdx int64, np *NodePointer) NodeRef { - if np.store == cs.reader { - if np.id.IsLeaf() { - // return NodeRef(np.id) - return NodeRef(NewNodeRelativePointer(true, int64(np.fileIdx))) - } else { - // for branch nodes the relative offset is the difference between the parent ID index and the branch ID index - relOffset := int64(np.fileIdx) - parentIdx - return NodeRef(NewNodeRelativePointer(false, relOffset)) - } - } else { - return NodeRef(np.id) - } -} - func (cs *ChangesetWriter) TotalBytes() int { return cs.leavesData.Size() + cs.branchesData.Size() + diff --git a/iavlx/cleanup.go b/iavlx/cleanup.go index 2c8021463a40..048dd75daac3 100644 --- a/iavlx/cleanup.go +++ b/iavlx/cleanup.go @@ -199,16 +199,6 @@ func (cp *cleanupProc) processEntry(ctx context.Context, entry, nextEntry *chang return fmt.Errorf("evicted/disposed changeset: %s found in queue", cs.files.dir) } - // safety check - ensure info is valid - if cs.info == nil { - return fmt.Errorf("changeset has nil info: %s found in queue", cs.files.dir) - } - - err := cs.FlushOrphans() - if err != nil { - return fmt.Errorf("failed to flush orphans for changeset %s: %w", cs.files.dir, err) - } - if cp.opts.DisableCompaction { return nil } @@ -224,14 +214,14 @@ func (cp *cleanupProc) processEntry(ctx context.Context, entry, nextEntry *chang // add to active compactor slog.DebugContext(ctx, "joining changeset to active compactor", "info", cs.info, "size", cs.TotalBytes(), "dir", cs.files.dir, "newDir", cp.activeCompactor.files.dir) - err = cp.activeCompactor.AddChangeset(cs) + err := cp.activeCompactor.AddChangeset(cs) if err != nil { return fmt.Errorf("failed to add changeset to active compactor: %w", err) } cp.beingCompacted = append(cp.beingCompacted, compactionEntry{entry: entry, cs: cs}) return nil } else { - err = cp.sealActiveCompactor() + err := cp.sealActiveCompactor() if err != nil { cp.cleanupFailedCompaction() return fmt.Errorf("failed to seal active compactor: %w", err) @@ -240,7 +230,7 @@ func (cp *cleanupProc) processEntry(ctx context.Context, entry, nextEntry *chang } // mark any pending orphans here when we don't have an active compactor - err = cp.doMarkOrphans() + err := cp.doMarkOrphans() if err != nil { cp.logger.Error("failed to mark orphans", "error", err) } diff --git a/iavlx/compactor.go b/iavlx/compactor.go index 0a90f48a3d12..6ceed1252f05 100644 --- a/iavlx/compactor.go +++ b/iavlx/compactor.go @@ -40,6 +40,7 @@ type Compactor struct { leafOrphanVersionTotal uint64 branchOrphanVersionTotal uint64 ctx context.Context + retainedOrphans map[NodeID]uint32 } func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { @@ -85,6 +86,7 @@ func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, s versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile), keyCache: make(map[string]uint32), offsetCache: make(map[NodeID]uint32), + retainedOrphans: make(map[NodeID]uint32), } // Process first changeset immediately @@ -107,7 +109,16 @@ func (c *Compactor) processChangeset(reader *Changeset) error { numVersions := versionsData.Count() leavesData := reader.leavesData branchesData := reader.branchesData - skippedBranches := 0 + + // flush orphan writer to ensure all orphans are written before reading + err := reader.orphanWriter.Flush() + if err != nil { + return fmt.Errorf("failed to flush orphan writer before reading orphan map: %w", err) + } + orphanMap, err := ReadOrphanMap(reader.files.orphansFile) + if err != nil { + return fmt.Errorf("failed to read orphan map: %w", err) + } slog.DebugContext(c.ctx, "processing changeset for compaction", "versions", numVersions) for i := 0; i < numVersions; i++ { @@ -123,14 +134,15 @@ func (c *Compactor) processChangeset(reader *Changeset) error { for j := uint32(0); j < leafCount; j++ { leaf := *leavesData.UnsafeItem(leafStartOffset + j) // copy id := leaf.Id - retain := leaf.OrphanVersion == 0 || c.criteria(uint32(id.Version()), leaf.OrphanVersion) + orphanVersion := orphanMap[id] + retain := orphanVersion == 0 || c.criteria(uint32(id.Version()), orphanVersion) if !retain { continue } - if leaf.OrphanVersion != 0 { + if orphanVersion != 0 { c.leafOrphanCount++ - c.leafOrphanVersionTotal += uint64(leaf.OrphanVersion) + c.leafOrphanVersionTotal += uint64(orphanVersion) } if newLeafStartIdx == 0 { @@ -163,6 +175,8 @@ func (c *Compactor) processChangeset(reader *Changeset) error { } c.offsetCache[id] = uint32(c.leavesWriter.Count()) + + c.retainedOrphans[id] = orphanVersion } newBranchStartIdx := uint32(0) @@ -174,15 +188,15 @@ func (c *Compactor) processChangeset(reader *Changeset) error { for j := uint32(0); j < branchCount; j++ { branch := *branchesData.UnsafeItem(branchStartOffset + j) // copy id := branch.Id - retain := branch.OrphanVersion == 0 || c.criteria(uint32(id.Version()), branch.OrphanVersion) + orphanVersion := orphanMap[id] + retain := orphanVersion == 0 || c.criteria(uint32(id.Version()), orphanVersion) if !retain { - skippedBranches++ continue } - if branch.OrphanVersion != 0 { + if orphanVersion != 0 { c.branchOrphanCount++ - c.branchOrphanVersionTotal += uint64(branch.OrphanVersion) + c.branchOrphanVersionTotal += uint64(orphanVersion) } if newBranchStartIdx == 0 { @@ -221,6 +235,8 @@ func (c *Compactor) processChangeset(reader *Changeset) error { return fmt.Errorf("failed to append branch %s: %w", id, err) } c.offsetCache[id] = uint32(c.branchesWriter.Count()) + + c.retainedOrphans[id] = orphanVersion } verInfo = VersionInfo{ @@ -241,7 +257,7 @@ func (c *Compactor) processChangeset(reader *Changeset) error { err := c.versionsWriter.Append(&verInfo) if err != nil { - return fmt.Errorf("failed to append version info for version %d: %w", reader.info.StartVersion+uint32(i), err) + return fmt.Errorf("failed to append version info for version %d: %w", reader.files.info.StartVersion+uint32(i), err) } } @@ -266,8 +282,8 @@ func (c *Compactor) Seal() (*Changeset, error) { } info := c.files.info - info.StartVersion = c.processedChangesets[0].info.StartVersion - info.EndVersion = c.processedChangesets[len(c.processedChangesets)-1].info.EndVersion + info.StartVersion = c.processedChangesets[0].files.info.StartVersion + info.EndVersion = c.processedChangesets[len(c.processedChangesets)-1].files.info.EndVersion info.LeafOrphans = c.leafOrphanCount info.BranchOrphans = c.branchOrphanCount info.LeafOrphanVersionTotal = c.leafOrphanVersionTotal @@ -277,7 +293,7 @@ func (c *Compactor) Seal() (*Changeset, error) { c.leavesWriter.Flush(), c.branchesWriter.Flush(), c.versionsWriter.Flush(), - c.files.infoMmap.Flush(), + c.files.RewriteInfo(), } if c.kvlogWriter != nil { errs = append(errs, c.kvlogWriter.Flush()) @@ -292,6 +308,12 @@ func (c *Compactor) Seal() (*Changeset, error) { return nil, fmt.Errorf("failed to initialize sealed changeset: %w", err) } + // write orphan map + err = cs.orphanWriter.WriteOrphanMap(c.retainedOrphans) + if err != nil { + return nil, fmt.Errorf("failed to write orphan map during compaction seal: %w", err) + } + return cs, nil } diff --git a/iavlx/leaf_layout.go b/iavlx/leaf_layout.go index 3f50cdb65423..11f90311e1d5 100644 --- a/iavlx/leaf_layout.go +++ b/iavlx/leaf_layout.go @@ -16,10 +16,9 @@ const ( ) type LeafLayout struct { - Id NodeID - KeyOffset uint32 - OrphanVersion uint32 // TODO 5 bytes? - Hash [32]byte + Id NodeID + Hash [32]byte + KeyOffset uint32 // TODO check if we have extra padding here } func (l LeafLayout) ID() NodeID { diff --git a/iavlx/mmap.go b/iavlx/mmap.go index e091d31af329..570538e0f1ae 100644 --- a/iavlx/mmap.go +++ b/iavlx/mmap.go @@ -28,7 +28,7 @@ func NewMmapFile(file *os.File) (*MmapFile, error) { } // maybe we can make read/write configurable? not sure if the OS optimizes read-only mapping - handle, err := mmap.Map(file, mmap.RDWR, 0) + handle, err := mmap.Map(file, mmap.RDONLY, 0) if err != nil { _ = file.Close() return nil, fmt.Errorf("failed to mmap file: %w", err) @@ -62,15 +62,6 @@ func (m *MmapFile) Data() []byte { return m.handle } -func (m *MmapFile) Flush() error { - if m.handle != nil { - if err := m.handle.Flush(); err != nil { - return fmt.Errorf("failed to flush mmap: %w", err) - } - } - return nil -} - func (m *MmapFile) Close() error { if m.handle != nil { handle := m.handle diff --git a/iavlx/node_id.go b/iavlx/node_id.go index 657e698e76ab..b21566a843cd 100644 --- a/iavlx/node_id.go +++ b/iavlx/node_id.go @@ -2,11 +2,11 @@ package iavlx import "fmt" -// bit 63 indicates whether this is a node ID (0) or relative pointer (1) -// a valid NodeID should always have bit 63 as 0 -// bit 62 indicates whether this is a leaf (1) or branch (0) -// bits 61-23 (39 bits) are for version -// bits 22-0 (23 bits) are for index +// NodeID is a stable identifier for a node in the IAVL tree. +// Bit 63 indicates whether this is a leaf (1) or branch (0) +// Bits 62-23 (40 bits) are for version. +// Bits 22-0 (23 bits) are for index. +// Valid index values start from 1. A zero index value may be used to indicate a null node. type NodeID uint64 func NewNodeID(isLeaf bool, version uint64, index uint32) NodeID { @@ -19,20 +19,20 @@ func NewNodeID(isLeaf bool, version uint64, index uint32) NodeID { } var id uint64 if isLeaf { - id |= 1 << 62 + id |= 1 << 63 } - id |= (version & 0x7FFFFFFFFF) << 23 + id |= (version & 0xFFFFFFFFFF) << 23 id |= uint64(index & 0x7FFFFF) return NodeID(id) } func (id NodeID) IsLeaf() bool { - // check if second highest bit is set - return id&(1<<62) != 0 + // check if highest bit is set + return id&(1<<63) != 0 } func (id NodeID) Version() uint64 { - return (uint64(id) >> 23) & 0x7FFFFF + return (uint64(id) >> 23) & 0xFFFFFFFFFF } func (id NodeID) Index() uint32 { @@ -42,80 +42,3 @@ func (id NodeID) Index() uint32 { func (id NodeID) String() string { return fmt.Sprintf("NodeID{leaf:%t, version:%d, index:%d}", id.IsLeaf(), id.Version(), id.Index()) } - -// bit 63 indicates whether this is a node ID (0) or relative pointer (1) -type NodeRef uint64 - -func (ref NodeRef) IsRelativePointer() bool { - return ref&(1<<63) != 0 -} - -func (ref NodeRef) IsNodeID() bool { - return ref&(1<<63) == 0 -} - -func (ref NodeRef) IsLeaf() bool { - return ref&(1<<62) != 0 -} - -func (ref NodeRef) AsNodeID() NodeID { - return NodeID(ref) -} - -func (ref NodeRef) AsRelativePointer() NodeRelativePointer { - return NodeRelativePointer(ref &^ (1 << 63)) -} - -func (ref NodeRef) String() string { - if ref.IsNodeID() { - return fmt.Sprintf("NodeRef(%s)", ref.AsNodeID()) - } else { - return fmt.Sprintf("NodeRef(%s)", ref.AsRelativePointer()) - } -} - -// bit 63 indicates whether this is a node ID (0) or relative pointer (1) -// a valid NodeRelativePointer should always have bit 63 as 0 -// bit 62 indicates whether this is a leaf (1) or branch (0) -// bits 61-0 (62 bits) are for signed offset -type NodeRelativePointer uint64 - -func NewNodeRelativePointer(isLeaf bool, offset int64) NodeRelativePointer { - // check offset fits in 61 bits signed - if offset < -0x1FFFFFFFFFFFFFFF || offset > 0x1FFFFFFFFFFFFFFF { - panic("offset too large for NodeRelativePointer") - } - var ptr uint64 - ptr |= 1 << 63 // set bit 63 to indicate relative pointer - if isLeaf { - ptr |= 1 << 62 - } - // Store absolute value of offset in bits 60-0 - // Use bit 61 as sign bit (1 = negative) - if offset < 0 { - ptr |= 1 << 61 // set sign bit - ptr |= uint64(-offset) & 0x1FFFFFFFFFFFFFFF // store absolute value in lower 61 bits - } else { - ptr |= uint64(offset) & 0x1FFFFFFFFFFFFFFF // store value in lower 61 bits - } - return NodeRelativePointer(ptr) -} - -func (ptr NodeRelativePointer) IsLeaf() bool { - // check if second highest bit is set - return ptr&(1<<62) != 0 -} - -func (ptr NodeRelativePointer) Offset() int64 { - // Extract the absolute value from lower 61 bits - offset := int64(ptr & 0x1FFFFFFFFFFFFFFF) - // if bit 61 is set, it's negative - if ptr&(1<<61) != 0 { - offset = -offset - } - return offset -} - -func (ptr NodeRelativePointer) String() string { - return fmt.Sprintf("NodeRelativePointer{leaf:%t, offset:%d}", ptr.IsLeaf(), ptr.Offset()) -} diff --git a/iavlx/options.go b/iavlx/options.go index df43ac0820e9..9aef5f93f150 100644 --- a/iavlx/options.go +++ b/iavlx/options.go @@ -1,15 +1,13 @@ package iavlx +import "time" + type Options struct { // EvictDepth defines the depth at which eviction occurs. 255 means no eviction. EvictDepth uint8 `json:"evict_depth"` // WriteWAL enables write-ahead logging for durability WriteWAL bool `json:"write_wal"` - - // WalSyncBuffer controls WAL sync behavior: -1 = blocking fsync, 0 = async sync immediately (buffer=1), >0 = buffer size - WalSyncBuffer int `json:"wal_sync_buffer"` - // CompactWAL determines if KV data is copied during compaction (true) or reused (false) CompactWAL bool `json:"compact_wal"` // DisableCompaction turns off background compaction entirely @@ -36,20 +34,12 @@ type Options struct { ReaderUpdateInterval uint32 `json:"reader_update_interval"` // FsyncInterval defines how often to fsync WAL when using async mode (in millisconds). - FsyncInterval uint32 `json:"fsync_interval"` + FsyncInterval int `json:"fsync_interval"` // ZeroCopy attempts to reduce copying of buffers, but this isn't really implemented yet and may not even be safe to implement. ZeroCopy bool `json:"zero_copy"` } -// GetWalSyncBufferSize returns the actual buffer size to use (handling 0 = 1 case) -func (o Options) GetWalSyncBufferSize() int { - if o.WalSyncBuffer == 0 { - return 1 // 0 means async sync immediately with buffer of 1 - } - return o.WalSyncBuffer -} - // GetCompactionOrphanAge returns the orphan age threshold with default func (o Options) GetCompactionOrphanAge() uint32 { if o.CompactionOrphanAge == 0 { @@ -96,3 +86,14 @@ func (o Options) GetReaderUpdateInterval() uint32 { } return o.ReaderUpdateInterval } + +func (o Options) FsyncEnabled() bool { + return o.FsyncInterval != 0 +} + +func (o Options) GetFsyncInterval() time.Duration { + if o.FsyncInterval < 0 { + return 0 + } + return time.Millisecond * time.Duration(o.FsyncInterval) +} diff --git a/iavlx/orphans.go b/iavlx/orphans.go new file mode 100644 index 000000000000..dc5b9df4fb97 --- /dev/null +++ b/iavlx/orphans.go @@ -0,0 +1,56 @@ +package iavlx + +import ( + "bufio" + "encoding/binary" + "io" + "os" +) + +type OrphanWriter struct { + *FileWriter +} + +func NewOrphanWriter(file *os.File) *OrphanWriter { + return &OrphanWriter{ + FileWriter: NewFileWriter(file), + } +} + +func (ow *OrphanWriter) WriteOrphan(version uint32, id NodeID) error { + var bz [12]byte + binary.LittleEndian.PutUint32(bz[0:4], version) + binary.LittleEndian.PutUint64(bz[4:12], uint64(id)) + _, err := ow.Write(bz[:]) + return err +} + +func (ow *OrphanWriter) WriteOrphanMap(orphanMap map[NodeID]uint32) error { + for id, version := range orphanMap { + if err := ow.WriteOrphan(version, id); err != nil { + return err + } + } + + return ow.Flush() +} + +func ReadOrphanMap(file *os.File) (map[NodeID]uint32, error) { + orphanMap := make(map[NodeID]uint32) + rdr := bufio.NewReader(file) + var buf [12]byte + for { + _, err := rdr.Read(buf[:]) + if err != nil { + if err == io.EOF { + return orphanMap, nil + } + return nil, err + } + version := binary.LittleEndian.Uint32(buf[0:4]) + id := NodeID(binary.LittleEndian.Uint64(buf[4:12])) + if _, exists := orphanMap[id]; !exists { + orphanMap[id] = version + } + } +} diff --git a/iavlx/reader.go b/iavlx/reader.go index e130b4b53e83..b9c9c82ac469 100644 --- a/iavlx/reader.go +++ b/iavlx/reader.go @@ -59,10 +59,6 @@ func (df *StructMmap[T]) Count() int { return len(df.items) } -func (df *StructMmap[T]) Flush() error { - return df.file.Flush() -} - func (df *StructMmap[T]) TotalBytes() int { return df.file.TotalBytes() } diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go index 6dd2a24b7683..db5d20d5c89e 100644 --- a/iavlx/tree_store.go +++ b/iavlx/tree_store.go @@ -55,7 +55,7 @@ func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, e ts.cleanupProc = newCleanupProc(ts) - if options.WriteWAL && options.FsyncInterval > 0 { + if options.FsyncEnabled() { ts.syncQueue = NewNonBlockingQueue[*ChangesetWriter]() ts.syncDone = make(chan error) go ts.syncProc() @@ -214,12 +214,6 @@ func (ts *TreeStore) SaveRoot(version uint32, root *NodePointer, totalLeaves, to } default: } - } else { - // Otherwise, sync immediately - err := ts.currentWriter.files.kvlogFile.Sync() - if err != nil { - return fmt.Errorf("failed to sync WAL file: %w", err) - } } // Determine if we should create a reader @@ -306,7 +300,7 @@ func (ts *TreeStore) MarkOrphans(version uint32, nodeIds [][]NodeID) { } func (ts *TreeStore) syncProc() { - tick := time.NewTicker(time.Duration(ts.opts.FsyncInterval) * time.Millisecond) + tick := time.NewTicker(ts.opts.GetFsyncInterval()) defer close(ts.syncDone) for { <-tick.C diff --git a/iavlx/tree_test.go b/iavlx/tree_test.go index 1680fe7bc7da..30969eba4443 100644 --- a/iavlx/tree_test.go +++ b/iavlx/tree_test.go @@ -16,7 +16,7 @@ import ( corestore "cosmossdk.io/core/store" sdklog "cosmossdk.io/log" - "cosmossdk.io/store/types" + storetypes "cosmossdk.io/store/types" ) // TODO: this test isn't for expected behavior. @@ -54,7 +54,7 @@ func TestTree_ErrorsOnOldVersion(t *testing.T) { t.Run(tc.name, func(t *testing.T) { commitTree := tc.getTree() for range 7 { - tree := commitTree.CacheWrap().(*Tree) + tree := commitTree.CacheWrap().(storetypes.CacheKVStore) tree.Set([]byte{0}, []byte{1}) tree.Write() commitTree.Commit() @@ -72,7 +72,7 @@ func TestTree_NonExistentChangeset(t *testing.T) { require.NoError(t, err) for range 7 { - tree := commitTree.CacheWrap().(*Tree) + tree := commitTree.CacheWrap().(storetypes.CacheKVStore) tree.Set([]byte{0}, []byte{1}) tree.Write() commitTree.Commit() @@ -88,7 +88,7 @@ func TestBasicTest(t *testing.T) { defer os.RemoveAll(dir) commitTree, err := NewCommitTree(dir, Options{}, sdklog.NewNopLogger()) require.NoError(t, err) - tree := commitTree.CacheWrap().(types.CacheKVStore) + tree := commitTree.CacheWrap().(storetypes.CacheKVStore) tree.Set([]byte{0}, []byte{1}) // renderTree(t, tree) @@ -176,7 +176,6 @@ func testIAVLXSims(t *rapid.T) { DisableCompaction: true, ZeroCopy: false, EvictDepth: 0, - WalSyncBuffer: 0, CompactionOrphanRatio: 0, CompactionOrphanAge: 0, RetainVersions: 0, @@ -244,7 +243,7 @@ func (s *SimMachine) set(t *rapid.T) { // set in both trees updated, errV1 := s.treeV1.Set(key, value) require.NoError(t, errV1, "failed to set key in V1 tree") - branch := s.treeV2.CacheWrap().(types.CacheKVStore) + branch := s.treeV2.CacheWrap().(storetypes.CacheKVStore) branch.Set(key, value) branch.Write() // require.Equal(t, updated, updatedV2, "update status mismatch between V1 and V2 trees") @@ -263,7 +262,7 @@ func (s *SimMachine) get(t *rapid.T) { key := s.selectKey(t) valueV1, errV1 := s.treeV1.Get(key) require.NoError(t, errV1, "failed to get key from V1 tree") - valueV2 := s.treeV2.CacheWrap().(types.CacheKVStore).Get(key) + valueV2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Get(key) require.Equal(t, valueV1, valueV2, "value mismatch between V1 and V2 trees") expectedValue, found := s.existingKeys[string(key)] if found { @@ -289,7 +288,7 @@ func (s *SimMachine) delete(t *rapid.T) { // delete in both trees _, removedV1, errV1 := s.treeV1.Remove(key) require.NoError(t, errV1, "failed to remove key from V1 tree") - branch := s.treeV2.CacheWrap().(types.CacheKVStore) + branch := s.treeV2.CacheWrap().(storetypes.CacheKVStore) branch.Delete(key) branch.Write() // require.Equal(t, removedV1, removedV2, "removed status mismatch between V1 and V2 trees") @@ -354,7 +353,7 @@ func (s *SimMachine) debugDump(t *rapid.T) { iavl.WriteDOTGraph(graph1, s.treeV1.ImmutableTree, nil) t.Logf("V1 tree:\n%s", graph1.String()) // renderTree(t, s.treeV2.Branch()) - iter2 := s.treeV2.CacheWrap().(types.CacheKVStore).Iterator(nil, nil) + iter2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Iterator(nil, nil) s.debugDumpTree(t, iter2) } @@ -395,7 +394,7 @@ func (s *SimMachine) debugDumpTree(t *rapid.T, iter corestore.Iterator) { func (s *SimMachine) compareIterators(t *rapid.T, start, end []byte, ascending bool) { iter1, err1 := s.treeV1.Iterator(start, end, ascending) require.NoError(t, err1, "failed to create iterator for V1 tree") - iter2 := s.treeV2.CacheWrap().(types.CacheKVStore).Iterator(start, end) + iter2 := s.treeV2.CacheWrap().(storetypes.CacheKVStore).Iterator(start, end) compareIteratorsAtVersion(t, iter1, iter2) } From e88a4165ffdc2c30786ec5f1c05da8594b9337ff Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 4 Nov 2025 22:33:17 -0500 Subject: [PATCH 73/87] fix bug when fsync enabled --- iavlx/changeset_writer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/iavlx/changeset_writer.go b/iavlx/changeset_writer.go index 7f7a53f1f226..41f9f50b92ee 100644 --- a/iavlx/changeset_writer.go +++ b/iavlx/changeset_writer.go @@ -245,7 +245,6 @@ func (cs *ChangesetWriter) Seal() (*Changeset, error) { if err != nil { return nil, fmt.Errorf("failed to initialize owned changeset reader: %w", err) } - cs.files = nil cs.leavesData = nil cs.branchesData = nil cs.versionsData = nil From fa918d8feef333f610074d4c46d198b0ae3eefa2 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 5 Nov 2025 11:12:17 -0500 Subject: [PATCH 74/87] optimize LeafPersistent.Value to reduce copying --- iavlx/changeset.go | 23 +++++++++++++++++++---- iavlx/kvlog.go | 2 +- iavlx/leaf_persisted.go | 3 +-- iavlx/tree_store.go | 36 +++++++++++++++++++++++++++++------- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/iavlx/changeset.go b/iavlx/changeset.go index c071cb6e7b1f..a91edf0821c6 100644 --- a/iavlx/changeset.go +++ b/iavlx/changeset.go @@ -82,7 +82,7 @@ func (cr *Changeset) getVersionInfo(version uint32) (*VersionInfo, error) { func (cr *Changeset) ReadK(nodeId NodeID, offset uint32) (key []byte, err error) { if cr.evicted.Load() { - return cr.treeStore.ReadK(nodeId, offset) + return cr.treeStore.ReadK(nodeId) } cr.Pin() defer cr.Unpin() @@ -98,13 +98,12 @@ func (cr *Changeset) ReadK(nodeId NodeID, offset uint32) (key []byte, err error) func (cr *Changeset) ReadKV(nodeId NodeID, offset uint32) (key, value []byte, err error) { if cr.evicted.Load() { - return cr.treeStore.ReadKV(nodeId, offset) + return cr.treeStore.ReadKV(nodeId) } cr.Pin() defer cr.Unpin() - // TODO add an optimization when we only want to read and copy value - k, v, err := cr.kvLog.ReadKV(offset) + k, v, err := cr.kvLog.UnsafeReadKV(offset) if err != nil { return nil, nil, err } @@ -115,6 +114,22 @@ func (cr *Changeset) ReadKV(nodeId NodeID, offset uint32) (key, value []byte, er return copyKey, copyValue, nil } +func (cr *Changeset) ReadV(nodeId NodeID, offset uint32) (value []byte, err error) { + if cr.evicted.Load() { + return cr.treeStore.ReadV(nodeId) + } + cr.Pin() + defer cr.Unpin() + + _, v, err := cr.kvLog.UnsafeReadKV(offset) + if err != nil { + return nil, err + } + copyValue := make([]byte, len(v)) + copy(copyValue, v) + return copyValue, nil +} + func (cr *Changeset) ResolveLeaf(nodeId NodeID, fileIdx uint32) (LeafLayout, error) { if cr.evicted.Load() { return cr.treeStore.ResolveLeaf(nodeId) diff --git a/iavlx/kvlog.go b/iavlx/kvlog.go index db8999d2bda9..afdaa89ae7fe 100644 --- a/iavlx/kvlog.go +++ b/iavlx/kvlog.go @@ -37,7 +37,7 @@ func (kvs *KVLog) UnsafeReadK(offset uint32) (key []byte, err error) { return kvs.UnsafeSliceExact(int(offset)+4, int(lenKey)) } -func (kvs *KVLog) ReadKV(offset uint32) (key, value []byte, err error) { +func (kvs *KVLog) UnsafeReadKV(offset uint32) (key, value []byte, err error) { key, err = kvs.UnsafeReadK(offset) if err != nil { return nil, nil, err diff --git a/iavlx/leaf_persisted.go b/iavlx/leaf_persisted.go index 895cd952b844..8ebb1a5f9385 100644 --- a/iavlx/leaf_persisted.go +++ b/iavlx/leaf_persisted.go @@ -36,8 +36,7 @@ func (node *LeafPersisted) Key() ([]byte, error) { } func (node *LeafPersisted) Value() ([]byte, error) { - _, v, err := node.store.ReadKV(node.layout.Id, node.layout.KeyOffset) - return v, err + return node.store.ReadV(node.layout.Id, node.layout.KeyOffset) } func (node *LeafPersisted) Left() *NodePointer { diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go index db5d20d5c89e..287a63e66257 100644 --- a/iavlx/tree_store.go +++ b/iavlx/tree_store.go @@ -97,7 +97,7 @@ func (ts *TreeStore) getChangesetForVersion(version uint32) *Changeset { } } -func (ts *TreeStore) ReadK(nodeId NodeID, _ uint32) (key []byte, err error) { +func (ts *TreeStore) ReadK(nodeId NodeID) (key []byte, err error) { cs := ts.getChangesetForVersion(uint32(nodeId.Version())) cs.Pin() defer cs.Unpin() @@ -124,25 +124,47 @@ func (ts *TreeStore) ReadK(nodeId NodeID, _ uint32) (key []byte, err error) { return cs.ReadK(nodeId, offset) } -func (ts *TreeStore) ReadKV(nodeId NodeID, _ uint32) (key, value []byte, err error) { +func (ts *TreeStore) ReadKV(nodeId NodeID) (key, value []byte, err error) { + if !nodeId.IsLeaf() { + return nil, nil, fmt.Errorf("node %s is not a leaf", nodeId.String()) + } + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return nil, nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + cs.Pin() defer cs.Unpin() - if cs == nil { - return nil, nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + leaf, err := cs.ResolveLeaf(nodeId, 0) + if err != nil { + return nil, nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) } + return cs.ReadKV(nodeId, leaf.KeyOffset) +} + +func (ts *TreeStore) ReadV(nodeId NodeID) ([]byte, error) { + // TODO reduce code duplication with ReadKV + if !nodeId.IsLeaf() { - return nil, nil, fmt.Errorf("node %s is not a leaf", nodeId.String()) + return nil, fmt.Errorf("node %s is not a leaf", nodeId.String()) } + cs := ts.getChangesetForVersion(uint32(nodeId.Version())) + if cs == nil { + return nil, fmt.Errorf("no changeset found for version %d", nodeId.Version()) + } + + cs.Pin() + defer cs.Unpin() leaf, err := cs.ResolveLeaf(nodeId, 0) if err != nil { - return nil, nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) + return nil, fmt.Errorf("failed to resolve leaf %s: %w", nodeId.String(), err) } - return cs.ReadKV(nodeId, leaf.KeyOffset) + return cs.ReadV(nodeId, leaf.KeyOffset) } func (ts *TreeStore) ResolveLeaf(nodeId NodeID) (LeafLayout, error) { From aece48b4db4e61894f67ed2cf9ce044de813705f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 5 Nov 2025 12:37:43 -0500 Subject: [PATCH 75/87] optimize []byte -> string lookup in map --- iavlx/changeset_writer.go | 8 ++++++-- iavlx/compactor.go | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/iavlx/changeset_writer.go b/iavlx/changeset_writer.go index 41f9f50b92ee..dec664398cda 100644 --- a/iavlx/changeset_writer.go +++ b/iavlx/changeset_writer.go @@ -152,7 +152,7 @@ func (cs *ChangesetWriter) writeBranch(np *NodePointer, node *MemNode) error { } // TODO cache key offset in memory to avoid duplicate writes - keyOffset, ok := cs.keyCache[string(node.key)] + keyOffset, ok := cs.keyCache[unsafeBytesToString(node.key)] if !ok { var err error keyOffset, err = cs.kvlog.WriteK(node.key) @@ -223,7 +223,7 @@ func (cs *ChangesetWriter) writeLeaf(np *NodePointer, node *MemNode) error { np.fileIdx = uint32(cs.leavesData.Count()) np.store = cs.reader - cs.keyCache[string(node.key)] = keyOffset + cs.keyCache[unsafeBytesToString(node.key)] = keyOffset return nil } @@ -266,3 +266,7 @@ func (cs *ChangesetWriter) SyncWAL() error { } return cs.files.kvlogFile.Sync() } + +func unsafeBytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} diff --git a/iavlx/compactor.go b/iavlx/compactor.go index 6ceed1252f05..596f744a50b4 100644 --- a/iavlx/compactor.go +++ b/iavlx/compactor.go @@ -163,7 +163,7 @@ func (c *Compactor) processChangeset(reader *Changeset) error { } leaf.KeyOffset = offset - c.keyCache[string(k)] = offset + c.keyCache[unsafeBytesToString(k)] = offset } else { // When not compacting WAL, add offset delta leaf.KeyOffset += kvOffsetDelta @@ -217,7 +217,7 @@ func (c *Compactor) processChangeset(reader *Changeset) error { if err != nil { return fmt.Errorf("failed to read key for branch %s: %w", id, err) } - offset, ok := c.keyCache[string(k)] + offset, ok := c.keyCache[unsafeBytesToString(k)] if !ok { offset, err = c.kvlogWriter.WriteK(k) } From 3610389fa80e1aa27563bc6e562afd24f7ced9e8 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 5 Nov 2025 12:39:06 -0500 Subject: [PATCH 76/87] import fix --- iavlx/changeset_writer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/iavlx/changeset_writer.go b/iavlx/changeset_writer.go index dec664398cda..b7f93c32f946 100644 --- a/iavlx/changeset_writer.go +++ b/iavlx/changeset_writer.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "sync/atomic" + "unsafe" ) type ChangesetWriter struct { From 521cea7f3555fa5311960aa3712c9f81aad8a024 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Wed, 5 Nov 2025 12:49:31 -0500 Subject: [PATCH 77/87] use larger bufio.Writer buffer by default --- iavlx/writer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iavlx/writer.go b/iavlx/writer.go index 28e394c3ad6d..4c29e82a2b5d 100644 --- a/iavlx/writer.go +++ b/iavlx/writer.go @@ -15,7 +15,7 @@ type FileWriter struct { func NewFileWriter(file *os.File) *FileWriter { return &FileWriter{ - writer: bufio.NewWriter(file), + writer: bufio.NewWriterSize(file, 512*1024 /* 512kb */), // TODO: maybe we can have this as a config option? } } From cc7dac8c583666e5e3f2de9119e45ce9aed949f7 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 6 Nov 2025 16:46:22 -0500 Subject: [PATCH 78/87] use sync.Pool for sha256 hashers --- iavlx/node_hash.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/iavlx/node_hash.go b/iavlx/node_hash.go index 5ec8c967910a..89fadcc203c0 100644 --- a/iavlx/node_hash.go +++ b/iavlx/node_hash.go @@ -4,7 +4,9 @@ import ( "crypto/sha256" "encoding/binary" "fmt" + "hash" "io" + "sync" ) func computeAndSetHash(node *MemNode, leftHash, rightHash []byte) ([]byte, error) { @@ -17,8 +19,20 @@ func computeAndSetHash(node *MemNode, leftHash, rightHash []byte) ([]byte, error return h, nil } +var hasherPool = sync.Pool{ + New: func() any { + return sha256.New() + }, +} + +func putBackHasher(h hash.Hash) { + h.Reset() + hasherPool.Put(h) +} + func computeHash(node Node, leftHash, rightHash []byte) ([]byte, error) { - hasher := sha256.New() + hasher := hasherPool.Get().(hash.Hash) + defer putBackHasher(hasher) if err := writeHashBytes(node, leftHash, rightHash, hasher); err != nil { return nil, err } @@ -27,6 +41,15 @@ func computeHash(node Node, leftHash, rightHash []byte) ([]byte, error) { var emptyHash = sha256.New().Sum(nil) +func shaSum256(bz []byte) []byte { + hasher := hasherPool.Get().(hash.Hash) + defer putBackHasher(hasher) + hasher.Write(bz) + var sum [sha256.Size]byte + hasher.Sum(sum[:0]) + return sum[:] +} + // Writes the node's hash to the given `io.Writer`. This function recursively calls // children to update hashes. func writeHashBytes(node Node, leftHash, rightHash []byte, w io.Writer) error { @@ -67,8 +90,7 @@ func writeHashBytes(node Node, leftHash, rightHash []byte, w io.Writer) error { // Indirection needed to provide proofs without values. // (e.g. ProofLeafNode.ValueHash) - valueHash := sha256.Sum256(value) - if err := encodeVarintPrefixedBytes(w, valueHash[:]); err != nil { + if err := encodeVarintPrefixedBytes(w, shaSum256(value)); err != nil { return fmt.Errorf("writing value, %w", err) } } else { From a51b055e5812713b3905391a40d6019e7c5b05fb Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Thu, 6 Nov 2025 16:46:31 -0500 Subject: [PATCH 79/87] update analysis --- analysis/analysis.ipynb | 146 +++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 45 deletions(-) diff --git a/analysis/analysis.ipynb b/analysis/analysis.ipynb index 348ae187d486..d5e03cd12506 100644 --- a/analysis/analysis.ipynb +++ b/analysis/analysis.ipynb @@ -6,8 +6,8 @@ "metadata": { "collapsed": true, "ExecuteTime": { - "end_time": "2025-11-04T22:30:42.717510Z", - "start_time": "2025-11-04T22:30:42.714513Z" + "end_time": "2025-11-05T21:35:56.923025Z", + "start_time": "2025-11-05T21:35:56.920193Z" } }, "source": [ @@ -16,44 +16,52 @@ "import pandas as pd" ], "outputs": [], - "execution_count": 17 + "execution_count": 6 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T22:30:43.898313Z", - "start_time": "2025-11-04T22:30:42.757981Z" + "end_time": "2025-11-05T21:35:58.646050Z", + "start_time": "2025-11-05T21:35:56.937299Z" } }, "cell_type": "code", - "source": "runs = load_otel_runs(\"/Users/arc/iavl-bench-data/sims\")", + "source": "runs = load_otel_runs(\"/Users/arc/iavl-bench-data/sims2\")", "id": "1d7a28bf36057a9d", "outputs": [], - "execution_count": 18 + "execution_count": 7 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T22:30:46.442075Z", - "start_time": "2025-11-04T22:30:43.908082Z" + "end_time": "2025-11-05T21:36:03.528568Z", + "start_time": "2025-11-05T21:35:58.656243Z" } }, "cell_type": "code", "source": [ - "pd.DataFrame(\n", - " [{'run': name, 'duration': block_summary(run).total_duration_seconds} for name, run in runs.items()]).set_index(\n", - " 'run')\n" + "summary_data = []\n", + "for name, run in runs.items():\n", + " summary = block_summary(run)\n", + " summary_data.append({\n", + " 'run': name,\n", + " 'duration': summary.total_duration_seconds,\n", + " 'blocks': summary.block_count,\n", + " 'avg_block_time': summary.total_duration_seconds / summary.block_count,\n", + " })\n", + "pd.DataFrame(summary_data).set_index('run')\n" ], "id": "7eee7d20b6a3ddfc", "outputs": [ { "data": { "text/plain": [ - " duration\n", - "run \n", - "iavlx-no-fsync 161.325176\n", - "iavl1 208.354362\n", - "iavlx 178.002386" + " duration blocks avg_block_time\n", + "run \n", + "iavl1-2 1694.763556 1000 1.694764\n", + "iavl1 597.953485 482 1.240567\n", + "iavlx 596.864307 718 0.831287\n", + "iavlx-2 1208.231131 1000 1.208231" ], "text/html": [ "
\n", @@ -75,42 +83,58 @@ " \n", " \n", " duration\n", + " blocks\n", + " avg_block_time\n", " \n", " \n", " run\n", " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " iavlx-no-fsync\n", - " 161.325176\n", + " iavl1-2\n", + " 1694.763556\n", + " 1000\n", + " 1.694764\n", " \n", " \n", " iavl1\n", - " 208.354362\n", + " 597.953485\n", + " 482\n", + " 1.240567\n", " \n", " \n", " iavlx\n", - " 178.002386\n", + " 596.864307\n", + " 718\n", + " 0.831287\n", + " \n", + " \n", + " iavlx-2\n", + " 1208.231131\n", + " 1000\n", + " 1.208231\n", " \n", " \n", "\n", "
" ] }, - "execution_count": 19, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 19 + "execution_count": 8 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T22:30:47.996699Z", - "start_time": "2025-11-04T22:30:46.493102Z" + "end_time": "2025-11-05T21:36:07.257996Z", + "start_time": "2025-11-05T21:36:03.574598Z" } }, "cell_type": "code", @@ -126,14 +150,14 @@ "width": 2 }, "mode": "lines", - "name": "iavlx-no-fsync", + "name": "iavl1-2", "x": { "dtype": "i2", - "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" }, "y": { "dtype": "f8", - "bdata": "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" + "bdata": "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" }, "type": "scatter" }, @@ -145,11 +169,11 @@ "name": "iavl1", "x": { "dtype": "i2", - "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAQ==" }, "y": { "dtype": "f8", - "bdata": "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" + "bdata": "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" }, "type": "scatter" }, @@ -161,11 +185,27 @@ "name": "iavlx", "x": { "dtype": "i2", - "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgI=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx-2", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" }, "y": { "dtype": "f8", - "bdata": "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" + "bdata": "dZMYBNYGqUBxPQrXo4ufQB1aZDvf4pNAm8QgsHKonEDdJAaBFWKzQIGVQ4ssjqJAqMZLN8mXpEBYObTIdpGOQAisHFpEuLpArBxaZDuOnkAMAiuH1lOgQM/3U+OlU6BAZTvfT42zdEDP91PjZVuoQNNNYhAYBKRA16NwPcp4qkDByqFFdpKqQG8Sg8AKDKRAWmQ7309qmkBaZDvfz2uVQDEIrBwaXKBAwvUoXI96k0AdWmQ73xtzQAIrhxbZmaBAf2q8dJM0pEB9PzVe+s2qQFg5tMh2c6JAZDvfT41HTEDdJAaBlaNOQCPb+X5q9GBA9ihcj8KlQkCOl24Sg2A2QIcW2c73g0JASOF6FK4nQUBmZmZmZoZCQPdT46WbZD9AxCCwcmixU0CIFtnO9/tWQBKDwMqhJTxAJQaBlUMrXkD6fmq8dMNDQGMQWDm0OE1AVg4tsp0teUC6SQwCK69TQARWDi2yjVJAlkOLbOfbQkArhxbZzp9fQLKd76fG20RAWDm0yHaeT0BxPQrXowBKQJZDi2zn60NAi2zn+6nhV0ChRbbz/RRTQIKVQ4ts5y9AikFg5dA6VECWQ4ts5/s9QCLb+X5q3D1AEoPAyqFVQUDtfD81XmpEQJqZmZmZGTJAWDm0yHYeREDz/dR46cZiQLKd76fG21NAku18PzW2XECYbhKDwAowQBsv3SQGQS5ATmIQWDm0LEBU46WbxMA3QDMzMzMzMzJAfT81XrpJMUB0kxgEVg4yQLXIdr6fmjFA5tAi2/neMUCF61G4HqUxQNejcD0KNzFAPQrXo3BdMUCyne+nxqsxQNEi2/l+6jBAokW28/20MkAJrBxaZLsxQN0kBoGVIzNAd76fGi/dN0C+nxov3SQxQJDC9ShcjzNAYeXQItsZMUDRItv5fqowQDm0yHa+/zBAhxbZzvdzMEAehetRuB4vQBxaZDvfjy5AiBbZzvfTL0C4HoXrUbgvQE5iEFg5lDBA+FPjpZsEL0Dn+6nx0s00QKjGSzeJgTFAxSCwcmhRMUD0/dR46eYwQPP91Hjp5i5APN9PjZduLkD4U+Olm2QwQN0kBoGVwy9AfT81XroJL0BlO99PjZcuQCPb+X6qPqtAJzEIrNyhrUAYBFYObaWtQD0K16PwT6tAR+F6FK52uUB/arx0E0ezQL6fGi9dM5BADi2yne+pg0AxCKwc2g+gQPT91HhpupBAm8QgsHIvlkBSuB6F6395QLXIdr7fRa9AMzMzMzNgoUA3iUFgJf+rQJhuEoNAM6JArkfhehRXpEBOYhBYOb+6QBSuR+HahcBAoBov3cRUs0BGtvP9VHyfQML1KFyPyK1AN4lBYOX0e0AMAiuHFmyRQC/dJAYB759Af2q8dBNYs0CsHFpku8iRQKRwPQrXiJJACKwcWsSatEDb+X5qvN2lQN9PjZduIl1ApHA9Cte7ZkDLoUW28y1uQBbZzvdT81pARrbz/dRYNEAIrBxaZBtLQAwCK4cWeTpAPQrXo3DFZUBmZmZmZtZBQIlBYOXQQlNAfT81Xrp3jEAxCKwc2t2yQDeJQWDlTn1AokW28/0ws0C38/3U+OqvQBSuR+GaVLNATDeJQYDvsUAOLbKdL3euQDIIrBxapH9ANV66SYylm0D6fmq8FJawQARWDi2yNJxAYhBYOTRPrECsHFpku9WlQFK4HoWrw6hArkfhelQApUDD9ShcT3qxQDeJQWDlVKVA9ihcj0IWm0DHSzeJwSexQLpJDAIrF0tARIts5/spS0CsHFpkO0deQCuHFtnO10NABFYOLbI1WUBpke18Py1kQHNoke18f0JAJQaBlUMvdkBKDAIrh+ZpQGu8dJMY7FxAdZMYBFauVEAj2/l+ahw3QLpJDAIrJ0tA5/up8dItNEBs5/up8bI5QEoMAiuHNj1AsHJoke18PkBpke18PwVRQGZmZmZmHlNAdZMYBFZ+QECiRbbz/XRVQNNNYhBYGVBAjZduEoNwZEB56SYxCCBgQPyp8dJNUkpAgZVDi2xTZEDdJAaBlatQQLTIdr6fSl5AUI2XbhJDTkC38/3UeFlGQFK4HoXr4VJAvp8aL92kSEAgsHJokaVwQNNNYhBYaU9A7FG4HoVLXUArhxbZzncwQN0kBoGVe1BAXI/C9ShcNEC7SQwCKwc0QOf7qfHSrTNACtejcD3qOUBpke18PzU1QNnO91PjBTRAjZduEoNgM0Dy0k1iEHgzQIbrUbgeZTJAke18PzV+MkCe76fGS7cyQGMQWDm06DFA4XoUrkeBMkCWQ4ts57syQIGVQ4tsBzNAj8L1KFyvMkCcxCCwcigzQPYoXI/CVTJAWDm0yHY+M0CS7Xw/NZ40QJhuEoPAWkBA8KfGSzcpM0CxcmiR7RwzQFK4HoXrsTJAaZHtfD+1NECyne+nxusyQClcj8L1CDJAOrTIdr6fMkCmm8QgsJIyQLgehetRWDJAwvUoXI+iMkB9PzVeumkyQP7UeOkmkTJAL90kBoG1MkAZBFYOLVIyQEoMAiuHFjNAf2q8dJOYMkCmm8QgsDIyQIXrUbgepTJA8dJNYhBYMkBqvHSTGIQzQP7UeOkmcTJAke18PzXeMkBg5dAi25k6QHa+nxovvTJAfT81XrppM0Av3SQGgbUyQAisHFpkmzJAxCCwcmhRM0DdJAaBlSMzQGiR7Xw/dTJAtMh2vp9aM0B3vp8aLx0zQKwcWmQ7nzNAZmZmZmYmNkBiEFg5tGgzQLpJDAIrRzNAMgisHFrkMUBPjZduEgNAQPhT46Wb5DJAN4lBYOVwM0AMAiuHFtkyQDZeukkMYjNAVg4tsp3PNUAYBFYOLdI3QESLbOf7OUBAcmiR7Xz/NEB/arx0kzgzQHe+nxovXTRAxks3iUFANUAK16NwPao1QKjGSzeJITZA6SYxCKy8OUCoxks3iYE0QNejcD0KFzRA5dAi2/neMkDHSzeJQYAyQKJFtvP9VDJAZDvfT403NEAaL90kBmE1QNEi2/l+SjNAxCCwcmiRM0BvEoPAyqE3QC2yne+npjZACKwcWmQbN0DIdr6fGi8+QLpJDAIrhzZA0SLb+X4KNkC1yHa+n/o0QMqhRbbzrUZAZmZmZmaGNkAv3SQGgRU3QNv5fmq8FDZA+FPjpZvENkDjpZvEIDA2QML1KFyPgjZAcD0K16MwN0DTTWIQWNk3QDeJQWDlcDdA8/3UeOlGN0BYObTIdl42QPp+arx0kzVAhxbZzvcTNkB0kxgEVo41QCcxCKwc+jVALbKd76fmNUCiRbbz/fQ1QG8Sg8DKgTRAikFg5dCiNEC9dJMYBNY0QFyPwvUofDRASQwCK4fWNEBMN4lBYIU0QPYoXI/C1UJAFtnO91PjMkACK4cW2U4yQOF6FK5HATJAtMh2vp/aMUDHSzeJQYAyQJqZmZmZ2TFA5tAi2/neMUD0/dR46WYyQN0kBoGVwzFAsp3vp8brMUAOLbKd74cyQL6fGi/dxDFAUrgehevRMUCfGi/dJKYyQG3n+6nxcjJAl24Sg8AqMkB56SYxCMwyQBFYObTIdjJAKVyPwvWoMkBdj8L1KFwyQE1iEFg5lDJAwMqhRbZTMkCXbhKDwGoyQPLSTWIQGDNAw/UoXI9CQEBBYOXQInszQFyPwvUoHDNA+FPjpZukMkCJQWDl0MIxQJvEILByiDhAhutRuB5FMkBYObTIdh4yQEjhehSupzFAQDVeuknMMUDLoUW2890xQNnO91PjpTFA001iEFj5MkCDwMqhRVY0QI/C9ShcjzhAke18PzX+MkBKDAIrh/YzQOomMQisPDNAWDm0yHY+M0CYbhKDwAo0QE5iEFg5dDVAexSuR+H6MUD0/dR46YYxQDEIrBxaRDJAwvUoXI8iMkCkcD0K1yM9QKjGSzeJ4TJAl24Sg8BKMUDl0CLb+V4yQBkEVg4tEjNAikFg5dCCOEDXo3A9Cpc1QNejcD0KdzNAVOOlm8QAMkDjpZvEINAyQM3MzMzMjDJAK4cW2c53MkDtfD81XlozQHnpJjEIjDJAQDVeukmMMUB3vp8aLx0xQBFYObTIvlpAIbByaNFqqUAkBoGVA7eyQJ3vp8bLo5BAJQaBlUOXZUAzMzMzs8yuQFyPwvUILLZAfT81Xtpbt0DrUbgeBYaUQBgEVg7tBr9AokW2832ov0BxPQrXoyRjQOOlm8QgdapAJzEIrJx2sEDVeOkmseymQF66SQwCF4dAxks3iQGJoUAQWDm0CA6wQEA1XroJF61AikFg5dDhsUAzMzMzMx6wQBov3SRGjKdAlkOLbOfVgEBlO99PjZNsQLbz/dS4wapATDeJQaCyo0A/NV66yVewQGu8dJNYZq1AfT81XrrDkkCBlUOLbMKRQNnO91Nj2adAu0kMAutookB/arx049jAQAwCK4eWcLNAVg4tsp2TaUBwPQrX43KiQEs3iUGgKapA+VPjpZvIf0DufD81XoyiQBxaZDvfA5FAlUOLbIewtEDJdr6fGiWGQB1aZDv/B75AdZMYBFY7nEAGgZVDi/OSQDEIrBx6v75AbOf7qbFJtECfGi/dhJe0QFTjpZuEg69ADAIrhxZKi0ArhxbZzuK7QH9qvHST7J5ATDeJQSA+oUD2KFyP4sawQIcW2c73UYRAwMqhRbZjfkAbL90kBmFGQB+F61G44m9Ay6FFtvNdYkCwcmiR7XxnQPp+arx0+1JAJzEIrByqUUBxPQrXo1BSQPLSTWIQsFRAL90kBoFdVUAW2c73U7NOQGiR7Xw/VUdA+FPjpZuESECgGi/dJCZcQJVDi2zn61RANV66SQyCPEBCYOXQIiNeQI2XbhKDyFNAexSuR+E6OUA3iUFg5RBMQObQItv5HjlAJQaBlUNLMkAhsHJokQ1TQFPjpZvE4C9AVOOlm8R4ekBWDi2ynU9WQLByaJHtzEtAGy/dJAafekDhehSuR6t/QEJg5dAimz9A1XjpJjFITEBvEoPAyhlQQCPb+X5qzFtA9P3UeOlWRUBiEFg5tBhLQGDl0CLbOVNACtejcD1aUEC+nxov3eQwQFK4HoXrkTJA4XoUrkdBMkAZBFYOLZIxQKJFtvP9VDFAa7x0kxiEL0DhehSuR8WWQKrx0k1i0npAikFg5RD1o0CiRbbzvVylQLKd76eGcbFAaJHtfL9BkUAhsHJoESebQJVDi2xn7ZxAGy/dJAaSnkAUrkfh+jOiQHWTGATWLJNAoUW2833pokBEi2zne3qcQDEIrBwaPLJA+FPjpZtyrEAv3SQGgdU/QFYOLbKd/1tA9P3UeOmGSUDkpZvEIJA/QJVDi2znu0BA2c73U+P1SUDO91PjpQtZQHA9CtejcDNA/Knx0k2CPEC5HoXrUdBqQMuhRbbz3URAwcqhRbZTQ0DXo3A9CrdBQLByaJHtHD1AQmDl0CI7N0AL16NwPQo7QPUoXI/C1TNABFYOLbJ9NkDx0k1iEBg2QDEIrBxaZDtAnxov3SQGNkAdWmQ73z9AQC/dJAaBdTNAMQisHFrUR0AOLbKd72tlQGZmZmZm5kxAEFg5tMhGVUDD9Shcj1JEQIGVQ4tsB0lAvXSTGARmSUCUGARWDoVhQLfz/dR46U9AXrpJDAJ7RUDZzvdT4/FtQJLtfD81XkxACKwcWmTzUEBMN4lBYFlsQE5iEFg51DRAj8L1KFwvNUC28/3UeKk1QMzMzMzMDDRAj8L1KFzvMkA830+Nl84yQP7UeOkmETJA7Xw/NV6aM0CYbhKDwAozQCuHFtnONzJAwvUoXI8iMkAEVg4tsn0yQMdLN4lBYDJAHVpkO9+vMUC9dJMYBFYxQO+nxks3STFAvp8aL91EMUCMbOf7qZExQCYxCKwc2i9A5/up8dKNMECq8dJNYlAwQJHtfD81ni9AaJHtfD8VMEB6FK5H4dowQMHKoUW2szBAJzEIrBw6MUB1kxgEVg4xQJLtfD81PjFAyXa+nxrPMEB1kxgEVi4wQOSlm8QgUDxAdr6fGi89MkCkcD0K18M6QPp+arx0szFA6SYxCKy8MUAPLbKd70cxQMdLN4lBYC5ACtejcD3KL0DO91PjpZsvQMh2vp8aby5AlkOLbOf7LkD5fmq8dNMtQKFFtvP9FC9AkML1KFwPL0C+nxov3eQwQN9PjZdukjFA1XjpJjGIMUCBlUOLbMcxQMl2vp8ajzBAjpduEoOAL0Ce76fGS7cuQCUGgZVDizFArkfhehQuL0Ce76fGS1cwQARWDi2ynS1AbOf7qfHSLUA3iUFg5RAuQF66SQwCqy1A4E+Nl24SLkC/nxov3SQuQLByaJHtvC1AIbByaJEtL0DP91PjpVswQLgehetROC5ADi2yne+nLUDNzMzMzIw+QFg5tMh2fjJAj8L1KFxPMkDo+6nx0g0yQD0K16NwnTFA+n5qvHTzMUAlBoGVQ4syQDVeukkM4jJAFK5H4Xo0M0CuR+F6FA4zQIts5/upcTJAi2zn+6kxMkB3vp8aL70xQA8tsp3vJzFA6SYxCKx8MUB3vp8aL10xQNv5fmq89DFAQmDl0CL7MUCHFtnO9xMyQF66SQwCqzJAg8DKoUUWMkBpke18PxU5QIlBYOXQAjJAKVyPwvXIMUDdJAaBlWMyQKrx0k1i8DFAF9nO91OjMUDm0CLb+Z4xQARWDi2yfTJARIts5/uJMkCxcmiR7RwzQFCNl24SQzJAJQaBlUOLMkAOLbKd7wcyQD4K16NwHTRA8KfGSzfJOkA3iUFg5ZA6QCLb+X5qPDJAqMZLN4khMkA3iUFg5dAzQFCNl24S4zFAFK5H4Xq0MUCG61G4HmUyQEa28/3UmDFAVOOlm8QAMkDO91PjpbsxQPYoXI/CNTFAppvEILBSMUD+1HjpJtExQLpJDAIrZzFA46WbxCCQMUAi2/l+arwxQLXIdr6fmjFAfT81XrqpMkBxPQrXo9AxQCPb+X5qfDFAGQRWDi3yMUCoxks3icExQO58PzVe+jFAiUFg5dDCMUB7FK5H4VoxQP7UeOkmUTdABoGVQ4tMMkCoxks3iUEyQF2PwvUoXDJAWDm0yHaeMkD7qfHSTUIzQGiR7Xw/VTNAjpduEoMAM0CJQWDl0KIyQJhuEoNArKpACKwcWuRSl0CLbOf7qU20QPLSTWJQjKxArBxaZPtJu0A830+NF9ORQNejcD3KV6xA0SLb+b6fwECS7Xw/Nfq/QO58PzVeVm5ACtejcN0tv0D0/dR4aYqeQBfZzvfTSMBARrbz/TTosEDrUbgeBYScQFG4HoXLSLFAHoXrUZhltEApXI/C9TBxQML1KFwP2JxA46WbxKB7nkBGtvP91ECZQIPAyqFFE5RA46WbxKCfoUBCYOXQgoa2QCuHFtkONKFA30+Nl25dvEBGtvP91PhnQL6fGi99NLRAcT0K1+P3rEDtfD81XjeMQAmsHFoEQ8JAL90kBoETdEC+nxovnXCkQEjhehRuXqFAwMqhRRa5sUA6tMh2fm6kQML1KFwvO7ZAjZduEuOQuUDQItv5fkpOQArXo3B9/rhAhxbZzrcctEDjpZvEwBq6QG3n+6kRibdA4E+Nl24SukC5HoXrEUWxQNejcD0KNpNAx0s3iUGevUA730+Nl3KbQIcW2c53vLFARIts5/unmkDhehSuR6qWQJZDi2znWoxARIts57vjpUCBlUOLbHGsQP7UeOkGjbBAGARWDi2kfEBg5dAi29N+QIcW2c43Kq9AYhBYOfQgwkATg8DKoZeAQMQgsHLIg8FAzvdT4wWeskCR7Xw/VXezQO+nxktX0bJASgwCK+fsuUBU46WbhMikQNnO91ODzbNAoBov3YRWsECF61G4ngCqQPp+arx0lJ1AlUOLbOeaokDvp8ZLN2+YQBFYObSIY6tAbef7qbHys0CBlUOL7HOaQNejcD2q975A3SQGgbVEwkBmZmZmpp/CQDDdJAaBj5FAK4cW2W7fv0BYObTIdrGZQH9qvHRTL6lAUI2XblKwo0AlBoGVgxawQJvEILCyrqlAa7x0k5h9kUAxCKwcuuSwQOxRuB4Fx5VAWTm0yPb3m0AnMQisHBexQMUgsHKoc6FATDeJQcDTukCvR+F61FOyQOtRuB6FboJArkfhehSuqkBcj8L1yDCyQKabxCCwOHlARIts53vgnkBoke18P72vQKRwPQrXbIhAJzEIrNwIqEA830+Nl4qRQKabxCCwOKBAKVyPwjWZrEB1kxgEVr6yQHA9CtejgJtAYxBYOTRQnEDm0CLb+W2zQClcj8K1jKZAsp3vp0bYoEBFtvP91IiyQB1aZDuflKFAbhKDwEp9k0CWQ4tsZ4+qQCCwcmgREbJAKVyPwrWIwEB3vp8aL6uYQJ7vp8ZLXKZA76fGSze9hkBaZDvfzzGXQMdLN4mBhq1AtvP91PjAm0BANV66SZ6qQMl2vp+aWa5Af2q8dBPmkEDFILBySFe0QArXo3D9qrRA+X5qvPSZskDC9ShcjyeIQLpJDALrVa9AcD0K16MEYUDP91PjpStzQIcW2c73G11ANV66SQxSRUD+1HjpJllSQHNoke1802tAqMZLN4k5ZECkcD0K1+ttQMdLN4lBYEBAOrTIdr4jYEBH4XoUrrduQG8Sg8DKyVdAOrTIdr6fQUAGgZVDi3RUQPCnxks3GVtAoBov3SSGM0B56SYxCLRQQJzEILBy+EVArkfhehSOQkCMbOf7qYFhQOF6FK5HEVpA9ihcj8KFWkC8dJMYBDY7QBBYObTIZkdABFYOLbJVV0CwcmiR7Zw5QLgehetR6FBAVg4tsp0PTkBxPQrXo5hQQGq8dJMYxERA16NwPQp3M0BOYhBYOYhiQA8tsp3vpzlAKVyPwvWoVkDvp8ZLN4VqQKRwPQrXYz5AHVpkO99vQECXbhKDwIo6QNNNYhBYuThAJzEIrBzaN0CHFtnO9xM4QMHKoUW28zZAMQisHFqENkBaZDvfTw04QGHl0CLbWThAQWDl0CJbOEAtsp3vpyY2QBbZzvdTIzhAnxov3STGOUC0yHa+X+mmQNz5fmr8TbFArBxaZDsZekCgGi/dJDSOQAvXo3B9g6RAjpduEkOPtUBYObTItluhQDvfT42XwJFAaZHtfP9Dq0ApXI/C9YKhQEW28/0UI6pAC9ejcD2jrUBGtvP91NRpQIpBYOUQYKdACKwcWqRcpUDsUbgeBTyyQEa28/00Nb1AZDvfTw3xkEAtsp3vJ0HDQCPb+X5q0JpAyXa+n4pLxUBkO99PjQmxQKwcWmQ7I65AWmQ738+jqEAK16Nw3RG1QM/3U+M1WMFA5tAi2/mccEAEVg4t0jC5QLKd76dWqMBAne+nxqvNw0BxPQrXY3ypQGQ7308NxL5ANDMzMzPfbEBrvHST+O3BQCuHFtk+TcBAsHJokQ1xu0BkO99PbT65QBkEVg6tm55AW2Q7309dZUA730+NFzSbQJZDi2znV2hAJzEIrHwYtEB9PzVeegS3QNnO91ODi7BAGQRWDm3JrUDNzMzMTICmQL+fGi/dZ6VA7FG4HoWba0Ai2/l+atxtQIcW2c73Z2NAzvdT4yWmm0Bcj8L1qB+wQHe+nxovWZtAVg4tsh1+r0BzaJHtPGiyQLFyaJHNhbBAI9v5fio5rUCXbhKDwHyCQDzfT42XwIBA16NwPQrXg0Dm0CLbOTKvQJDC9Si89bZA46WbxOCxq0Db+X5q/IKuQGZmZmYmC6tAPQrXo7Ctq0CcxCCw8gyiQCuHFtnORLBAf2q8dNOXo0CiRbbz/cqDQEa28/3Uq4RAWDm0yHZOakAnMQisHLWlQIcW2c73WolALbKd76emW0A/NV66SXxvQMHKoUW2E0xASzeJQWBVVUDXo3A9CgdGQO+nxks3aUxAFtnO91PjV0AfhetRuGZSQBSuR+F6HnRAuB6F61HoS0AshxbZzrdPQIcW2c73U0FAdr6fGi9dOkBI4XoUrkc8QB1aZDvff0hACtejcD06akCsHFpkO29DQDVeukkMqllADi2yne+HMkArhxbZzndEQJQYBFYOnVFAuB6F61H4QUA5tMh2vl88QBODwMqhBTdAzczMzMwUV0BEi2zn+wlDQBODwMqhlUpAmpmZmZl9Z0BfukkMAjtPQE5iEFg5xERAsXJoke38ZkDqJjEIrHxOQCUGgZVDO0RAmpmZmZkJQUDjpZvEINA1QKfGSzeJYU5ANV66SQyCOkBQjZduEoM0QKabxCCwsjRA5KWbxCDQNEAtsp3vp4Y0QL10kxgEFjVAu0kMAisnNUCwcmiR7Zw0QH9qvHSTeDRACtejcD0KNUAEVg4tsj01QFG4HoXrETVAKVyPwvUoNUCoxks3iaE0QMP1KFyPAjVAd76fGi8dRkAlBoGVQ4s5QOxRuB6FazlAoBov3SRGOECJQWDl0CI3QI/C9ShcrzhAYhBYObRIOEAj2/l+avw4QLfz/dR46TVABoGVQ4vsNUBDi2zn+6k1QFg5tMh23jVAtch2vp8aNUBt5/up8dIzQPUoXI/C9TNAZTvfT403M0CTGARWDk00QH0/NV66iTNAzczMzMxMMkCOl24SgwAzQG4Sg8DKgTJA2/l+arxUMkAZBFYOLZIyQN0kBoGVQzJAhetRuB7lMkDZzvdT4wUyQBKDwMqh9UFAxks3ieHPsUCJQWDlUGSRQLx0kxgEgqxAObTIdn5HpkA=" }, "type": "scatter" } @@ -971,13 +1011,13 @@ "output_type": "display_data" } ], - "execution_count": 20 + "execution_count": 9 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T22:30:49.462496Z", - "start_time": "2025-11-04T22:30:48.036322Z" + "end_time": "2025-11-05T21:36:10.802903Z", + "start_time": "2025-11-05T21:36:07.300201Z" } }, "cell_type": "code", @@ -993,14 +1033,14 @@ "width": 2 }, "mode": "lines", - "name": "iavlx-no-fsync", + "name": "iavl1-2", "x": { "dtype": "i2", - "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" }, "y": { "dtype": "f8", - "bdata": "16NwPQpXIEBqvHSTGEQjQEA1XrpJjCRAF9nO91NjIUBYObTIdn4lQCuHFtnONyJA8tJNYhCYJECYbhKDwMoaQDvfT42XbiFAEoPAyqHFIUDn+6nx0g0gQFpkO99PjSNA7nw/NV66BEDfT42XbhLxP5ZDi2zn++0/F9nO91Pj5T9OYhBYObTsP4/C9Shcj+o/qvHSTWIQ6D8rhxbZzvfrP3e+nxov3eg/g8DKoUW26z9aZDvfT43rP6rx0k1iEPI/xks3iUFg8T8X2c73U+PtP9Ei2/l+avw/tvP91Hjp7j9mZmZmZmbqP46XbhKDwOo/DAIrhxbZ6j9zaJHtfD/zP+kmMQisHOo/j8L1KFyP6j8nMQisHFroP46XbhKDwOY/XI/C9Shc+z9ANV66SQzqP2ZmZmZmZuo/fT81XrpJ6D89CtejcD3qPxgEVg4tsgBAGQRWDi2y8T9Di2zn+6npPx1aZDvfzx1AVOOlm8SgGkDGSzeJQaAgQFK4HoXr0SBA001iEFg5DEBvEoPAyqEQQHE9CtejcARA7nw/NV46HkCoxks3iQEpQDQzMzMzsxpAxks3iUFgDEC28/3UeOkTQBbZzvdT4wpA9ihcj8J1GEBEi2zn+6kDQE5iEFg5tBNAL90kBoEVHECwcmiR7XwAQD0K16NwPRlAUI2XbhKD/j/qJjEIrBwQQEFg5dAi2yBAGARWDi2yFEB/arx0kxgsQJ8aL90kBiJA001iEFi5FECsHFpkO98ZQJVDi2zn+xhAzczMzMzMCUBqvHSTGATwPy/dJAaBle8/XrpJDAIr8T+yne+nxkvxPwvXo3A9Cu8/PzVeukkM7j/azvdT46XvP2ZmZmZmZu4/QmDl0CLb7T/dJAaBlUPrP0w3iUFg5fA/TmIQWDm0/D/RItv5fmr0P/yp8dJNYvA/xks3iUFg9T+ZmZmZmZnzP1pkO99PjfM/HVpkO99P8T8fhetRuB7zP90kBoGVQwxA5dAi2/l+8D8j2/l+arzwPwisHFpkO/E/nMQgsHJo8T8Sg8DKoUXwPwisHFpkO/U/PzVeukkM7j+d76fGSzfxP1+6SQwCK+8/TWIQWDm08j/jpZvEILDuP9NNYhBYOf4/+n5qvHSTAED+1HjpJjHwP7x0kxgEVvA/SQwCK4cW8T+6SQwCK4fuP5qZmZmZme0/xks3iUFg7T9Di2zn+6npP4ts5/up8e4/PzVeukkM7j9uEoPAyqHtP0W28/3U+BJAKVyPwvUo8j/6fmq8dJPwP6abxCCwcvA/aJHtfD817j8pXI/C9SjwPx1aZDvfT/E/v58aL90k6j+8dJMYBFbqPxBYObTIdvA/6SYxCKwc7j9qvHSTGIQjQDvfT42XbhhAmpmZmZmZBEBwPQrXo/AeQMdLN4lB4BtA5dAi2/l+GkDkpZvEILAQQMl2vp8aLwhADy2yne8nFUD6fmq8dJMiQIGVQ4tsZxlAbxKDwMqhDEDHSzeJQWAKQHE9CtejcApAAiuHFtnO+z8PLbKd7ycdQC2yne+nRhFAf2q8dJOYIkBg5dAi23kiQOJ6FK5HoSZAv58aL90kBkAdWmQ7388ZQDiJQWDlEClAQmDl0CLbJUBANV66SUwsQIcW2c738zFAlBgEVg5tJkD6fmq8dFMrQBBYObTI9hdAMQisHFpkAkDNzMzMzAwjQEOLbOf7qS1AJQaBlUMLJEDJdr6fGi8UQHnpJjEIbC5A/dR46SaxH0Dn+6nx0k0pQO+nxks3SShAVOOlm8RgJkCuR+F6FO4wQN0kBoGVwy1AvXSTGARWBkDufD81XnoiQHE9CtejMClA61G4HoVrFkB1kxgEVg4cQCUGgZVDCxpAOrTIdr5fMkA4iUFg5VARQPP91HjpZjNAHVpkO98PMEAfhetRuJ4oQJ8aL90kRipAOrTIdr5fMUAaL90kBkEpQH0/NV66SQ1AF9nO91PjJEAlBoGVQxtBQAwCK4cWmSJADAIrhxaZIUBKDAIrh5YZQHA9CtejEDBAg8DKoUW2AkD2KFyPwvX0P39qvHSTGPg/5KWbxCCgREAbL90kBoHzP7bz/dR46fA/Di2yne+n8j/GSzeJQWD1PwaBlUOLbPM/PzVeukkM9D8MAiuHFtnyP30/NV66SfA/qvHSTWIQ/D9CYOXQItv1P6rx0k1iEPQ/BoGVQ4ts9T9eukkMAiv1P7x0kxgEVvA/SOF6FK5H9T/8qfHSTeITQA4tsp3vp/I/QDVeukkM8j+gGi/dJAbxPyCwcmiR7fA/eekmMQisAkD6fmq8dJPwP2iR7Xw/NfA/rkfhehSu8T+4HoXrUbjyPyGwcmiR7QdAyqFFtvP98j9iEFg5tMjwPyyHFtnO9+8/AAAAAAAA8D+CwMqhRbbvP++nxks3ifE/46WbxCCw8j9kO99PjZfwP/p+arx0k/Q/Dy2yne+n8D/n+6nx0k3wP+F6FK5H4fA/WDm0yHa+7z8tsp3vp8bxP6jGSzeJQfA/PgrXo3A98D89CtejcD3uP7pJDAIrh+4/XI/C9Shc8T+wcmiR7XzxP9ijcD0K1/E/f2q8dJMY8j8PLbKd76fwPx1aZDvfT+0/wMqhRbbz8T9oke18PzXyPylcj8L1KPA/arx0kxgE8D/eJAaBlUPxP1K4HoXrUfA/AAAAAAAA8D8=" + "bdata": "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" }, "type": "scatter" }, @@ -1012,11 +1052,11 @@ "name": "iavl1", "x": { "dtype": "i2", - "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAQ==" }, "y": { "dtype": "f8", - "bdata": "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" + "bdata": "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" }, "type": "scatter" }, @@ -1028,11 +1068,27 @@ "name": "iavlx", "x": { "dtype": "i2", - "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gA=" + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgI=" + }, + "y": { + "dtype": "f8", + "bdata": "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" + }, + "type": "scatter" + }, + { + "line": { + "width": 2 + }, + "mode": "lines", + "name": "iavlx-2", + "x": { + "dtype": "i2", + "bdata": "AQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkQCSAJMAlACVAJYAlwCYAJkAmgCbAJwAnQCeAJ8AoAChAKIAowCkAKUApgCnAKgAqQCqAKsArACtAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8AL0AvgC/AMAAwQDCAMMAxADFAMYAxwDIAMkAygDLAMwAzQDOAM8A0ADRANIA0wDUANUA1gDXANgA2QDaANsA3ADdAN4A3wDgAOEA4gDjAOQA5QDmAOcA6ADpAOoA6wDsAO0A7gDvAPAA8QDyAPMA9AD1APYA9wD4APkA+gD7APwA/QD+AP8AAAEBAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wEAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeAh8CIAIhAiICIwIkAiUCJgInAigCKQIqAisCLAItAi4CLwIwAjECMgIzAjQCNQI2AjcCOAI5AjoCOwI8Aj0CPgI/AkACQQJCAkMCRAJFAkYCRwJIAkkCSgJLAkwCTQJOAk8CUAJRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJgAmECYgJjAmQCZQJmAmcCaAJpAmoCawJsAm0CbgJvAnACcQJyAnMCdAJ1AnYCdwJ4AnkCegJ7AnwCfQJ+An8CgAKBAoICgwKEAoUChgKHAogCiQKKAosCjAKNAo4CjwKQApECkgKTApQClQKWApcCmAKZApoCmwKcAp0CngKfAqACoQKiAqMCpAKlAqYCpwKoAqkCqgKrAqwCrQKuAq8CsAKxArICswK0ArUCtgK3ArgCuQK6ArsCvAK9Ar4CvwLAAsECwgLDAsQCxQLGAscCyALJAsoCywLMAs0CzgLPAtAC0QLSAtMC1ALVAtYC1wLYAtkC2gLbAtwC3QLeAt8C4ALhAuIC4wLkAuUC5gLnAugC6QLqAusC7ALtAu4C7wLwAvEC8gLzAvQC9QL2AvcC+AL5AvoC+wL8Av0C/gL/AgADAQMCAwMDBAMFAwYDBwMIAwkDCgMLAwwDDQMOAw8DEAMRAxIDEwMUAxUDFgMXAxgDGQMaAxsDHAMdAx4DHwMgAyEDIgMjAyQDJQMmAycDKAMpAyoDKwMsAy0DLgMvAzADMQMyAzMDNAM1AzYDNwM4AzkDOgM7AzwDPQM+Az8DQANBA0IDQwNEA0UDRgNHA0gDSQNKA0sDTANNA04DTwNQA1EDUgNTA1QDVQNWA1cDWANZA1oDWwNcA10DXgNfA2ADYQNiA2MDZANlA2YDZwNoA2kDagNrA2wDbQNuA28DcANxA3IDcwN0A3UDdgN3A3gDeQN6A3sDfAN9A34DfwOAA4EDggODA4QDhQOGA4cDiAOJA4oDiwOMA40DjgOPA5ADkQOSA5MDlAOVA5YDlwOYA5kDmgObA5wDnQOeA58DoAOhA6IDowOkA6UDpgOnA6gDqQOqA6sDrAOtA64DrwOwA7EDsgOzA7QDtQO2A7cDuAO5A7oDuwO8A70DvgO/A8ADwQPCA8MDxAPFA8YDxwPIA8kDygPLA8wDzQPOA88D0APRA9ID0wPUA9UD1gPXA9gD2QPaA9sD3APdA94D3wPgA+ED4gPjA+QD5QPmA+cD6AM=" }, "y": { "dtype": "f8", - "bdata": "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" + "bdata": "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" }, "type": "scatter" } @@ -1838,13 +1894,13 @@ "output_type": "display_data" } ], - "execution_count": 21 + "execution_count": 10 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-04T22:30:49.479104Z", - "start_time": "2025-11-04T22:30:49.476589Z" + "end_time": "2025-11-05T21:36:10.820435Z", + "start_time": "2025-11-05T21:36:10.818144Z" } }, "cell_type": "code", From 92e9d804b3d3eca2c7536839275391f3dd845c86 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Fri, 14 Nov 2025 21:52:52 -0500 Subject: [PATCH 80/87] feat(iavlx): reload database --- iavlx/changeset_files.go | 179 ++++++++++++++++++++++++++++++-------- iavlx/changeset_writer.go | 2 +- iavlx/compactor.go | 5 +- iavlx/load.go | 64 ++++++++++++++ iavlx/tree_store.go | 7 +- 5 files changed, 220 insertions(+), 37 deletions(-) create mode 100644 iavlx/load.go diff --git a/iavlx/changeset_files.go b/iavlx/changeset_files.go index 36d84dfd2607..6108a9dff75e 100644 --- a/iavlx/changeset_files.go +++ b/iavlx/changeset_files.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "strings" ) type ChangesetFiles struct { @@ -14,6 +16,7 @@ type ChangesetFiles struct { compactedAt uint32 kvlogFile *os.File + kvlogPath string branchesFile *os.File leavesFile *os.File versionsFile *os.File @@ -24,7 +27,7 @@ type ChangesetFiles struct { closed bool } -func OpenChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogPath string) (*ChangesetFiles, error) { +func CreateChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogPath string) (*ChangesetFiles, error) { // ensure absolute path var err error treeDir, err = filepath.Abs(treeDir) @@ -43,62 +46,149 @@ func OpenChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlogP return nil, fmt.Errorf("failed to create changeset dir: %w", err) } + // create pending marker file for compacted changesets + if compactedAt > 0 { + err := os.WriteFile(filepath.Join(dir, "pending"), []byte{}, 0o644) + if err != nil { + return nil, fmt.Errorf("failed to create pending marker file for compacted changeset: %w", err) + } + } + + localKVLogPath := filepath.Join(dir, "kv.log") if kvlogPath == "" { - kvlogPath = filepath.Join(dir, "kv.log") + kvlogPath = localKVLogPath + } else { + // create symlink to kvlog so that it can be reopened later + err := os.Symlink(kvlogPath, localKVLogPath) + if err != nil { + return nil, fmt.Errorf("failed to create kvlog symlink: %w", err) + } + kvlogPath, err = filepath.EvalSymlinks(localKVLogPath) + if err != nil { + return nil, fmt.Errorf("failed to eval kvlog symlink: %w", err) + } } - kvlogFile, err := os.OpenFile(kvlogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + + cr := &ChangesetFiles{ + dir: dir, + treeDir: treeDir, + startVersion: startVersion, + compactedAt: compactedAt, + kvlogPath: kvlogPath, + } + + err = cr.open(os.O_RDWR | os.O_CREATE | os.O_APPEND) if err != nil { - return nil, fmt.Errorf("failed to create KV log file: %w", err) + return nil, fmt.Errorf("failed to open changeset files: %w", err) + } + return cr, nil +} + +func ReopenChangesetFiles(dirName string) (*ChangesetFiles, error) { + startVersion, compactedAt, valid := ParseChangesetDirName(dirName) + if !valid { + return nil, fmt.Errorf("invalid changeset dir name: %s", dirName) } - leavesPath := filepath.Join(dir, "leaves.dat") - leavesFile, err := os.OpenFile(leavesPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + dir, err := filepath.Abs(dirName) if err != nil { - return nil, fmt.Errorf("failed to create leaves data file: %w", err) + return nil, fmt.Errorf("failed to get absolute path for %s: %w", dirName, err) } - branchesPath := filepath.Join(dir, "branches.dat") - branchesFile, err := os.OpenFile(branchesPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + treeDir := filepath.Dir(dir) + + localKVLogPath := filepath.Join(dir, "kv.log") + kvlogPath, err := filepath.EvalSymlinks(localKVLogPath) if err != nil { - return nil, fmt.Errorf("failed to create branches data file: %w", err) + return nil, fmt.Errorf("failed to eval kvlog symlink: %w", err) } - versionsPath := filepath.Join(dir, "versions.dat") - versionsFile, err := os.OpenFile(versionsPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + cr := &ChangesetFiles{ + dir: dir, + treeDir: treeDir, + startVersion: uint32(startVersion), + compactedAt: uint32(compactedAt), + kvlogPath: kvlogPath, + } + + err = cr.open(os.O_RDWR) if err != nil { - return nil, fmt.Errorf("failed to create versions data file: %w", err) + return nil, fmt.Errorf("failed to open changeset files: %w", err) } - orphansPath := filepath.Join(dir, "orphans.dat") - orphansFile, err := os.OpenFile(orphansPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + return cr, nil +} + +func (cr *ChangesetFiles) open(mode int) error { + var err error + leavesPath := filepath.Join(cr.dir, "leaves.dat") + + cr.kvlogFile, err = os.OpenFile(cr.kvlogPath, mode, 0o644) if err != nil { - return nil, fmt.Errorf("failed to create orphans data file: %w", err) + return fmt.Errorf("failed to create KV log file: %w", err) } - infoPath := filepath.Join(dir, "info.dat") - infoFile, err := os.OpenFile(infoPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + cr.leavesFile, err = os.OpenFile(leavesPath, mode, 0o644) if err != nil { - return nil, fmt.Errorf("failed to create changeset info file: %w", err) + return fmt.Errorf("failed to create leaves data file: %w", err) } - info, err := ReadChangesetInfo(infoFile) + branchesPath := filepath.Join(cr.dir, "branches.dat") + cr.branchesFile, err = os.OpenFile(branchesPath, mode, 0o644) if err != nil { - return nil, fmt.Errorf("failed to read changeset info: %w", err) + return fmt.Errorf("failed to create branches data file: %w", err) } - return &ChangesetFiles{ - dir: dir, - treeDir: treeDir, - startVersion: startVersion, - compactedAt: compactedAt, - kvlogFile: kvlogFile, - branchesFile: branchesFile, - leavesFile: leavesFile, - versionsFile: versionsFile, - orphansFile: orphansFile, - infoFile: infoFile, - info: info, - }, nil + versionsPath := filepath.Join(cr.dir, "versions.dat") + cr.versionsFile, err = os.OpenFile(versionsPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create versions data file: %w", err) + } + + orphansPath := filepath.Join(cr.dir, "orphans.dat") + cr.orphansFile, err = os.OpenFile(orphansPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create orphans data file: %w", err) + } + + infoPath := filepath.Join(cr.dir, "info.dat") + cr.infoFile, err = os.OpenFile(infoPath, mode, 0o644) + if err != nil { + return fmt.Errorf("failed to create changeset info file: %w", err) + } + + cr.info, err = ReadChangesetInfo(cr.infoFile) + if err != nil { + return fmt.Errorf("failed to read changeset info: %w", err) + } + + return nil +} + +func ParseChangesetDirName(dirName string) (startVersion, compactedAt uint64, valid bool) { + var err error + // if no dot, it's an original changeset + if !strings.Contains(dirName, ".") { + startVersion, err = strconv.ParseUint(dirName, 10, 64) + if err != nil { + return 0, 0, false + } + return startVersion, 0, true + } else { + parts := strings.Split(dirName, ".") + if len(parts) != 2 { + return 0, 0, false + } + startVersion, err = strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return 0, 0, false + } + compactedAt, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return 0, 0, false + } + return startVersion, compactedAt, true + } } func (cr *ChangesetFiles) TreeDir() string { @@ -125,6 +215,27 @@ func (cr *ChangesetFiles) RewriteInfo() error { return RewriteChangesetInfo(cr.infoFile, cr.info) } +func IsChangesetReady(dir string) (bool, error) { + pendingPath := filepath.Join(dir, "pending") + _, err := os.Stat(pendingPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return true, nil + } + return false, fmt.Errorf("failed to stat pending marker file: %w", err) + } + return false, nil +} + +func (cr *ChangesetFiles) MarkReady() error { + pendingPath := filepath.Join(cr.dir, "pending") + err := os.Remove(pendingPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to remove pending marker file: %w", err) + } + return nil +} + type ChangesetDeleteArgs struct { SaveKVLogPath string } diff --git a/iavlx/changeset_writer.go b/iavlx/changeset_writer.go index b7f93c32f946..a08f1410e0d1 100644 --- a/iavlx/changeset_writer.go +++ b/iavlx/changeset_writer.go @@ -24,7 +24,7 @@ type ChangesetWriter struct { } func NewChangesetWriter(treeDir string, startVersion uint32, treeStore *TreeStore) (*ChangesetWriter, error) { - files, err := OpenChangesetFiles(treeDir, startVersion, 0, "") + files, err := CreateChangesetFiles(treeDir, startVersion, 0, "") if err != nil { return nil, fmt.Errorf("failed to open changeset files: %w", err) } diff --git a/iavlx/compactor.go b/iavlx/compactor.go index 596f744a50b4..aa1c2ad109b6 100644 --- a/iavlx/compactor.go +++ b/iavlx/compactor.go @@ -62,7 +62,7 @@ func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, s kvlogPath = "" } - newFiles, err := OpenChangesetFiles(files.TreeDir(), files.StartVersion(), opts.CompactedAt, kvlogPath) + newFiles, err := CreateChangesetFiles(files.TreeDir(), files.StartVersion(), opts.CompactedAt, kvlogPath) if err != nil { return nil, fmt.Errorf("failed to open new changeset files: %w", err) } @@ -301,6 +301,9 @@ func (c *Compactor) Seal() (*Changeset, error) { if err := errors.Join(errs...); err != nil { return nil, fmt.Errorf("failed to flush data during compaction seal: %w", err) } + if err := c.files.MarkReady(); err != nil { + return nil, fmt.Errorf("failed to mark changeset as ready during compaction seal: %w", err) + } cs := NewChangeset(c.treeStore) err := cs.InitOwned(c.files) diff --git a/iavlx/load.go b/iavlx/load.go new file mode 100644 index 000000000000..705b8507e76c --- /dev/null +++ b/iavlx/load.go @@ -0,0 +1,64 @@ +package iavlx + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/tidwall/btree" +) + +func (ts *TreeStore) load() error { + // collect a list of all existing subdirectories + dirs, err := os.ReadDir(ts.dir) + if err != nil { + return fmt.Errorf("failed to read tree store dir: %w", err) + } + + // directory map: startVersion -> compactedAt -> dirName + var dirMap btree.Map[uint64, *btree.Map[uint64, string]] + for _, de := range dirs { + if !de.IsDir() { + continue + } + dir := filepath.Join(ts.dir, de.Name()) + startVersion, compactedAt, valid := ParseChangesetDirName(dir) + if !valid { + continue + } + if _, found := dirMap.Get(startVersion); !found { + dirMap.Set(startVersion, &btree.Map[uint64, string]{}) + } + caMap, _ := dirMap.Get(startVersion) + caMap.Set(compactedAt, dir) + } + + // load changesets in order + for { + startVersion, compactionMap, ok := dirMap.PopMin() + if !ok { + return nil + } + + lastCompaction, dirName, ok := compactionMap.PopMax() + if !ok { + return fmt.Errorf("internal error: no changeset entries for start version %d", startVersion) + } + + ready, err := IsChangesetReady(dirName) + if err != nil { + return fmt.Errorf("failed to check if changeset %s is ready: %w", dirName, err) + } + + if !ready { + ts.logger.Warn("found incomplete compaction, deleting", "dir", dirName) + err := os.RemoveAll(dirName) + if err != nil { + ts.logger.Error("failed to remove incomplete compaction", "dir", dirName, "error", err) + } + continue + } + + cf, err := ReopenChangesetFiles(dirName) + } +} diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go index 287a63e66257..5e93aacd498e 100644 --- a/iavlx/tree_store.go +++ b/iavlx/tree_store.go @@ -48,7 +48,12 @@ func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, e opts: options, } - err := ts.initNewWriter() + err := ts.load() + if err != nil { + return nil, fmt.Errorf("failed to load existing changesets: %w", err) + } + + err = ts.initNewWriter() if err != nil { return nil, fmt.Errorf("failed to initialize first writer: %w", err) } From 473ed33bf62b49dc2985671ffa1038746b60e5a0 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 17 Nov 2025 11:52:52 -0500 Subject: [PATCH 81/87] WIP on reopening --- iavlx/changeset.go | 13 ++++++++++ iavlx/changeset_files.go | 2 +- iavlx/load.go | 26 +++++++++++++++++-- iavlx/tree_test.go | 56 ++++++++++++++++++++++++---------------- 4 files changed, 72 insertions(+), 25 deletions(-) diff --git a/iavlx/changeset.go b/iavlx/changeset.go index a91edf0821c6..12b6ae0a6225 100644 --- a/iavlx/changeset.go +++ b/iavlx/changeset.go @@ -33,6 +33,19 @@ func NewChangeset(treeStore *TreeStore) *Changeset { } } +func OpenChangeset(treeStore *TreeStore, dir string) (*Changeset, error) { + files, err := OpenChangesetFiles(dir) + if err != nil { + return nil, fmt.Errorf("failed to open changeset files: %w", err) + } + cs := NewChangeset(treeStore) + err = cs.InitOwned(files) + if err != nil { + return nil, fmt.Errorf("failed to initialize changeset: %w", err) + } + return cs, nil +} + func (cr *Changeset) InitOwned(files *ChangesetFiles) error { err := cr.InitShared(files) if err != nil { diff --git a/iavlx/changeset_files.go b/iavlx/changeset_files.go index 6108a9dff75e..9511efd90a2a 100644 --- a/iavlx/changeset_files.go +++ b/iavlx/changeset_files.go @@ -84,7 +84,7 @@ func CreateChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlo return cr, nil } -func ReopenChangesetFiles(dirName string) (*ChangesetFiles, error) { +func OpenChangesetFiles(dirName string) (*ChangesetFiles, error) { startVersion, compactedAt, valid := ParseChangesetDirName(dirName) if !valid { return nil, fmt.Errorf("invalid changeset dir name: %s", dirName) diff --git a/iavlx/load.go b/iavlx/load.go index 705b8507e76c..e0f51cdfb565 100644 --- a/iavlx/load.go +++ b/iavlx/load.go @@ -33,6 +33,8 @@ func (ts *TreeStore) load() error { caMap.Set(compactedAt, dir) } + ts.savedVersion.Store(0) + ts.stagedVersion = 1 // load changesets in order for { startVersion, compactionMap, ok := dirMap.PopMin() @@ -40,11 +42,21 @@ func (ts *TreeStore) load() error { return nil } - lastCompaction, dirName, ok := compactionMap.PopMax() + _, dirName, ok := compactionMap.PopMax() if !ok { return fmt.Errorf("internal error: no changeset entries for start version %d", startVersion) } + if startVersion < uint64(ts.stagedVersion) { + ts.logger.Warn("found undeleted compaction", "startVersion", startVersion, "stagedVersion", ts.stagedVersion, "dir", dirName) + // TODO delete undeleted compactions + continue + } + + if startVersion > uint64(ts.stagedVersion) { + return fmt.Errorf("missing changeset for staged version %d", ts.stagedVersion) + } + ready, err := IsChangesetReady(dirName) if err != nil { return fmt.Errorf("failed to check if changeset %s is ready: %w", dirName, err) @@ -59,6 +71,16 @@ func (ts *TreeStore) load() error { continue } - cf, err := ReopenChangesetFiles(dirName) + cs, err := OpenChangeset(ts, dirName) + if err != nil { + return fmt.Errorf("failed to open changeset in %s: %w", dirName, err) + } + + ce := &changesetEntry{} + ce.changeset.Store(cs) + ts.changesets.Set(uint32(startVersion), ce) + + ts.savedVersion.Store(cs.info.EndVersion) + ts.stagedVersion = cs.info.EndVersion + 1 } } diff --git a/iavlx/tree_test.go b/iavlx/tree_test.go index 30969eba4443..9561075bded0 100644 --- a/iavlx/tree_test.go +++ b/iavlx/tree_test.go @@ -170,47 +170,54 @@ func testIAVLXSims(t *rapid.T) { tempDir, err := os.MkdirTemp("", "iavlx") require.NoError(t, err, "failed to create temp directory") defer os.RemoveAll(tempDir) - treeV2, err := NewCommitTree(tempDir, Options{ - WriteWAL: true, - CompactWAL: true, - DisableCompaction: true, - ZeroCopy: false, - EvictDepth: 0, - CompactionOrphanRatio: 0, - CompactionOrphanAge: 0, - RetainVersions: 0, - MinCompactionSeconds: 0, - ChangesetMaxTarget: 1, - CompactAfterVersions: 0, - ReaderUpdateInterval: 1, - }, sdklog.NewNopLogger()) - require.NoError(t, err, "failed to create iavlx tree") simMachine := &SimMachine{ treeV1: treeV1, - treeV2: treeV2, + dirV2: tempDir, existingKeys: map[string][]byte{}, } + simMachine.openV2Tree(t) // TODO switch from StateMachineActions to manually setting up the actions map, this is going to be too magical for other maintainers otherwise t.Repeat(map[string]func(*rapid.T){ - "": simMachine.Check, - "UpdateN": simMachine.UpdateN, - "GetN": simMachine.GetN, - "Iterate": simMachine.Iterate, - "Commit": simMachine.Commit, + "": simMachine.Check, + "UpdateN": simMachine.UpdateN, + "GetN": simMachine.GetN, + "Iterate": simMachine.Iterate, + "Commit": simMachine.Commit, + "CloseReopen": simMachine.CloseReopen, }) require.NoError(t, treeV1.Close(), "failed to close iavl tree") - require.NoError(t, treeV2.Close(), "failed to close iavlx tree") + require.NoError(t, simMachine.treeV2.Close(), "failed to close iavlx tree") } type SimMachine struct { treeV1 *iavl.MutableTree treeV2 *CommitTree + dirV2 string // existingKeys keeps track of keys that have been set in the tree or deleted. Deleted keys are retained as nil values. existingKeys map[string][]byte } +func (s *SimMachine) openV2Tree(t require.TestingT) { + var err error + s.treeV2, err = NewCommitTree(s.dirV2, Options{ + WriteWAL: true, + CompactWAL: true, + DisableCompaction: true, + ZeroCopy: false, + EvictDepth: 0, + CompactionOrphanRatio: 0, + CompactionOrphanAge: 0, + RetainVersions: 0, + MinCompactionSeconds: 0, + ChangesetMaxTarget: 1, + CompactAfterVersions: 0, + ReaderUpdateInterval: 1, + }, sdklog.NewNopLogger()) + require.NoError(t, err, "failed to create iavlx tree") +} + func (s *SimMachine) Check(t *rapid.T) { // after every operation verify the iavlx tree // after every operation we check that both trees are identical @@ -236,6 +243,11 @@ func (s *SimMachine) GetN(t *rapid.T) { } } +func (s *SimMachine) CloseReopen(t *rapid.T) { + require.NoError(t, s.treeV2.Close(), "failed to close iavlx tree") + s.openV2Tree(t) +} + func (s *SimMachine) set(t *rapid.T) { // choose either a new or an existing key key := s.selectKey(t) From 9cd5fbe22a2dfbcb6f8e2540017b785bf7415b89 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 17 Nov 2025 12:25:01 -0500 Subject: [PATCH 82/87] refactor version tracking --- iavlx/commit_tree.go | 17 +++++------------ iavlx/load.go | 2 -- iavlx/tree_store.go | 15 ++++++++------- iavlx/tree_test.go | 33 ++++++++++----------------------- 4 files changed, 23 insertions(+), 44 deletions(-) diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go index 749a418745cf..e99f17648410 100644 --- a/iavlx/commit_tree.go +++ b/iavlx/commit_tree.go @@ -15,7 +15,6 @@ import ( type CommitTree struct { latest atomic.Pointer[NodePointer] root *NodePointer - version uint32 writeMutex sync.Mutex store *TreeStore zeroCopy bool @@ -63,7 +62,7 @@ func (c *CommitTree) workingHash() []byte { } savedVersion := c.store.SavedVersion() - stagedVersion := c.stagedVersion() + stagedVersion := c.store.stagedVersion c.commitCtx = &commitContext{ version: stagedVersion, savedVersion: savedVersion, @@ -96,7 +95,7 @@ func (c *CommitTree) commit() (storetypes.CommitID, error) { // compute hash and assign node IDs hash := c.workingHash() - stagedVersion := c.stagedVersion() + stagedVersion := c.store.stagedVersion if c.writeWal { // wait for WAL write to complete err := <-c.walDone @@ -117,7 +116,7 @@ func (c *CommitTree) commit() (storetypes.CommitID, error) { // make sure we have a non-nil commit context commitCtx = &commitContext{} } - err := c.store.SaveRoot(stagedVersion, c.root, commitCtx.leafNodeIdx, commitCtx.branchNodeIdx) + err := c.store.SaveRoot(c.root, commitCtx.leafNodeIdx, commitCtx.branchNodeIdx) if err != nil { return storetypes.CommitID{}, err } @@ -130,7 +129,6 @@ func (c *CommitTree) commit() (storetypes.CommitID, error) { // cache the committed tree as the latest version c.latest.Store(c.root) - c.version++ commitId := storetypes.CommitID{ Version: int64(stagedVersion), Hash: hash, @@ -193,7 +191,7 @@ func (c *CommitTree) Set(key, value []byte) { c.writeMutex.Lock() defer c.writeMutex.Unlock() - stagedVersion := c.stagedVersion() + stagedVersion := c.store.stagedVersion leafNode := &MemNode{ height: 0, size: 1, @@ -228,7 +226,7 @@ func (c *CommitTree) Delete(key []byte) { c.walQueue.Send([]KVUpdate{{DeleteKey: key}}) } - ctx := &MutationContext{Version: c.stagedVersion()} + ctx := &MutationContext{Version: c.store.stagedVersion} _, newRoot, _, err := removeRecursive(c.root, key, ctx) if err != nil { panic(err) @@ -254,7 +252,6 @@ func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, er tree := &CommitTree{ root: nil, zeroCopy: opts.ZeroCopy, - version: 0, logger: logger, store: ts, evictionDepth: opts.EvictDepth, @@ -265,10 +262,6 @@ func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, er return tree, nil } -func (c *CommitTree) stagedVersion() uint32 { - return c.version + 1 -} - func (c *CommitTree) reinitWalProc() { if !c.writeWal { return diff --git a/iavlx/load.go b/iavlx/load.go index e0f51cdfb565..e852632b7c22 100644 --- a/iavlx/load.go +++ b/iavlx/load.go @@ -33,8 +33,6 @@ func (ts *TreeStore) load() error { caMap.Set(compactedAt, dir) } - ts.savedVersion.Store(0) - ts.stagedVersion = 1 // load changesets in order for { startVersion, compactionMap, ok := dirMap.PopMin() diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go index 5e93aacd498e..aa650e9931d8 100644 --- a/iavlx/tree_store.go +++ b/iavlx/tree_store.go @@ -42,10 +42,11 @@ type changesetEntry struct { func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, error) { ts := &TreeStore{ - dir: dir, - changesets: &btree.Map[uint32, *changesetEntry]{}, - logger: logger, - opts: options, + dir: dir, + changesets: &btree.Map[uint32, *changesetEntry]{}, + logger: logger, + opts: options, + stagedVersion: 1, } err := ts.load() @@ -217,14 +218,14 @@ func (ts *TreeStore) WriteWALCommit(version uint32) error { return ts.currentWriter.WriteWALCommit(version) } -func (ts *TreeStore) SaveRoot(version uint32, root *NodePointer, totalLeaves, totalBranches uint32) error { +func (ts *TreeStore) SaveRoot(root *NodePointer, totalLeaves, totalBranches uint32) error { + version := ts.stagedVersion ts.logger.Debug("saving root", "version", version) err := ts.currentWriter.SaveRoot(root, version, totalLeaves, totalBranches) if err != nil { return err } - - ts.stagedVersion = version + ts.stagedVersion++ currentSize := ts.currentWriter.TotalBytes() maxSize := ts.opts.GetChangesetMaxTarget() diff --git a/iavlx/tree_test.go b/iavlx/tree_test.go index 9561075bded0..337075e49c79 100644 --- a/iavlx/tree_test.go +++ b/iavlx/tree_test.go @@ -179,12 +179,11 @@ func testIAVLXSims(t *rapid.T) { // TODO switch from StateMachineActions to manually setting up the actions map, this is going to be too magical for other maintainers otherwise t.Repeat(map[string]func(*rapid.T){ - "": simMachine.Check, - "UpdateN": simMachine.UpdateN, - "GetN": simMachine.GetN, - "Iterate": simMachine.Iterate, - "Commit": simMachine.Commit, - "CloseReopen": simMachine.CloseReopen, + "": simMachine.Check, + "UpdateN": simMachine.UpdateN, + "GetN": simMachine.GetN, + "Iterate": simMachine.Iterate, + "Commit": simMachine.Commit, }) require.NoError(t, treeV1.Close(), "failed to close iavl tree") @@ -336,26 +335,14 @@ func (s *SimMachine) Commit(t *rapid.T) { require.NoError(t, err, "failed to save version in V1 tree") commitId2 := s.treeV2.Commit() require.NoError(t, err, "failed to save version in V2 tree") - // s.debugDump(t) err = VerifyTree(s.treeV2) - // if err != nil { - // branches := s.treeV2.rollingDiff.branchData - // n := branches.Count() - // buf := &bytes.Buffer{} - // for i := uint64(0); i < n; i++ { - // branch, err := branches.Branch(i) - // require.NoError(t, err, "failed to read branch") - // buf.WriteString(fmt.Sprintf("%d: %s\n", i+1, branch)) - // } - // require.NoError(t, os.WriteFile("branches_dump.txt", buf.Bytes(), 0o644)) - // - // buf = &bytes.Buffer{} - // require.NoError(t, s.treeV2.wal.DebugDump(buf)) - // require.NoError(t, os.WriteFile("wal_dump.txt", buf.Bytes(), 0o644)) - //} require.NoError(t, err, "failed to verify V2 tree") require.Equal(t, hash1, commitId2.Hash, "hash mismatch between V1 and V2 trees") - // require.Equal(t, v1, v2, "version mismatch between V1 and V2 trees") + //closeReopen := rapid.Bool().Draw(t, "closeReopen") + //if closeReopen { + // require.NoError(t, s.treeV2.Close()) + // s.openV2Tree(t) + //} } func (s *SimMachine) debugDump(t *rapid.T) { From 9078e38c44a1290b9399a3885612145335e7d47d Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 17 Nov 2025 13:20:41 -0500 Subject: [PATCH 83/87] fix reloading bugs --- iavlx/changeset.go | 4 ++ iavlx/changeset_files.go | 2 +- iavlx/commit_tree.go | 61 ++++++++++++++++++++----------- iavlx/load.go | 79 +++++++++++++++++++++++++++------------- iavlx/tree_test.go | 22 +++++------ 5 files changed, 108 insertions(+), 60 deletions(-) diff --git a/iavlx/changeset.go b/iavlx/changeset.go index 12b6ae0a6225..9f872924f102 100644 --- a/iavlx/changeset.go +++ b/iavlx/changeset.go @@ -368,6 +368,10 @@ func (cr *Changeset) ResolveRoot(version uint32) (*NodePointer, error) { if err != nil { return nil, err } + if vi.RootID == 0 { + // empty tree + return nil, nil + } return &NodePointer{ id: vi.RootID, store: cr, diff --git a/iavlx/changeset_files.go b/iavlx/changeset_files.go index 9511efd90a2a..81fbc4c6957d 100644 --- a/iavlx/changeset_files.go +++ b/iavlx/changeset_files.go @@ -85,7 +85,7 @@ func CreateChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlo } func OpenChangesetFiles(dirName string) (*ChangesetFiles, error) { - startVersion, compactedAt, valid := ParseChangesetDirName(dirName) + startVersion, compactedAt, valid := ParseChangesetDirName(filepath.Base(dirName)) if !valid { return nil, fmt.Errorf("invalid changeset dir name: %s", dirName) } diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go index e99f17648410..3b0c24f88dcb 100644 --- a/iavlx/commit_tree.go +++ b/iavlx/commit_tree.go @@ -35,8 +35,46 @@ type CommitTree struct { commitCtx *commitContext } -func (c *CommitTree) getRoot() *NodePointer { - return c.root +func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, error) { + ts, err := NewTreeStore(dir, opts, logger) + if err != nil { + return nil, fmt.Errorf("failed to create tree store: %w", err) + } + + var root *NodePointer + var lastCommitId storetypes.CommitID + savedVersion := ts.SavedVersion() + if savedVersion > 0 { + root, err = ts.ResolveRoot(savedVersion) + if err != nil { + return nil, fmt.Errorf("failed to resolve root for saved version %d: %w", savedVersion, err) + } + if root != nil { + rootNode, err := root.Resolve() + if err != nil { + return nil, fmt.Errorf("failed to resolve root node for saved version %d: %w", savedVersion, err) + } + hash := rootNode.Hash() + lastCommitId = storetypes.CommitID{ + Version: int64(savedVersion), + Hash: hash, + } + } + } + + tree := &CommitTree{ + store: ts, + root: root, + lastCommitId: lastCommitId, + zeroCopy: opts.ZeroCopy, + logger: logger, + evictionDepth: opts.EvictDepth, + writeWal: opts.WriteWAL, + } + tree.latest.Store(root) + tree.reinitWalProc() + + return tree, nil } func (c *CommitTree) WorkingHash() []byte { @@ -243,25 +281,6 @@ func (c *CommitTree) ReverseIterator(start, end []byte) storetypes.Iterator { return NewIterator(start, end, false, c.root, c.zeroCopy) } -func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, error) { - ts, err := NewTreeStore(dir, opts, logger) - if err != nil { - return nil, fmt.Errorf("failed to create tree store: %w", err) - } - - tree := &CommitTree{ - root: nil, - zeroCopy: opts.ZeroCopy, - logger: logger, - store: ts, - evictionDepth: opts.EvictDepth, - writeWal: opts.WriteWAL, - } - tree.reinitWalProc() - - return tree, nil -} - func (c *CommitTree) reinitWalProc() { if !c.writeWal { return diff --git a/iavlx/load.go b/iavlx/load.go index e852632b7c22..6adc19d016d7 100644 --- a/iavlx/load.go +++ b/iavlx/load.go @@ -21,11 +21,14 @@ func (ts *TreeStore) load() error { if !de.IsDir() { continue } - dir := filepath.Join(ts.dir, de.Name()) - startVersion, compactedAt, valid := ParseChangesetDirName(dir) + + dirName := de.Name() + startVersion, compactedAt, valid := ParseChangesetDirName(dirName) if !valid { continue } + + dir := filepath.Join(ts.dir, dirName) if _, found := dirMap.Get(startVersion); !found { dirMap.Set(startVersion, &btree.Map[uint64, string]{}) } @@ -40,13 +43,10 @@ func (ts *TreeStore) load() error { return nil } - _, dirName, ok := compactionMap.PopMax() - if !ok { - return fmt.Errorf("internal error: no changeset entries for start version %d", startVersion) - } + // startVersion should be equal to stagedVersion if startVersion < uint64(ts.stagedVersion) { - ts.logger.Warn("found undeleted compaction", "startVersion", startVersion, "stagedVersion", ts.stagedVersion, "dir", dirName) + ts.logger.Warn("found undeleted changeset that was already compacted", "startVersion", startVersion, "stagedVersion", ts.stagedVersion) // TODO delete undeleted compactions continue } @@ -55,30 +55,57 @@ func (ts *TreeStore) load() error { return fmt.Errorf("missing changeset for staged version %d", ts.stagedVersion) } - ready, err := IsChangesetReady(dirName) - if err != nil { - return fmt.Errorf("failed to check if changeset %s is ready: %w", dirName, err) - } + for { + _, dirName, ok := compactionMap.PopMax() + if !ok { + return fmt.Errorf("internal error: no changeset entries for start version %d", startVersion) + } - if !ready { - ts.logger.Warn("found incomplete compaction, deleting", "dir", dirName) - err := os.RemoveAll(dirName) + ready, err := IsChangesetReady(dirName) if err != nil { - ts.logger.Error("failed to remove incomplete compaction", "dir", dirName, "error", err) + return fmt.Errorf("failed to check if changeset %s is ready: %w", dirName, err) } - continue - } - cs, err := OpenChangeset(ts, dirName) - if err != nil { - return fmt.Errorf("failed to open changeset in %s: %w", dirName, err) - } + if !ready { + ts.logger.Warn("found incomplete compaction, deleting", "dir", dirName) + err := os.RemoveAll(dirName) + if err != nil { + ts.logger.Error("failed to remove incomplete compaction", "dir", dirName, "error", err) + } + continue + } + + ts.logger.Debug("loading changeset", "startVersion", startVersion, "dir", dirName) + + cs, err := OpenChangeset(ts, dirName) + if err != nil { + return fmt.Errorf("failed to open changeset in %s: %w", dirName, err) + } + + realStartVersion := cs.info.StartVersion + if uint64(realStartVersion) != startVersion { + if realStartVersion == 0 { + if dirMap.Len() != 0 { + return fmt.Errorf("found incomplete changeset %s, but there are later changesets present", dirName) + } + ts.logger.Debug("found final incomplete changeset, deleting", "dir", dirName) + err := os.RemoveAll(dirName) + if err != nil { + return fmt.Errorf("failed to remove incomplete changeset %s: %w", dirName, err) + } + break + } else { + return fmt.Errorf("changeset in %s has mismatched start version %d (expected %d)", dirName, realStartVersion, startVersion) + } + } - ce := &changesetEntry{} - ce.changeset.Store(cs) - ts.changesets.Set(uint32(startVersion), ce) + ce := &changesetEntry{} + ce.changeset.Store(cs) + ts.changesets.Set(uint32(startVersion), ce) - ts.savedVersion.Store(cs.info.EndVersion) - ts.stagedVersion = cs.info.EndVersion + 1 + ts.savedVersion.Store(cs.info.EndVersion) + ts.stagedVersion = cs.info.EndVersion + 1 + break + } } } diff --git a/iavlx/tree_test.go b/iavlx/tree_test.go index 337075e49c79..17e6e16a176b 100644 --- a/iavlx/tree_test.go +++ b/iavlx/tree_test.go @@ -198,7 +198,10 @@ type SimMachine struct { existingKeys map[string][]byte } -func (s *SimMachine) openV2Tree(t require.TestingT) { +func (s *SimMachine) openV2Tree(t interface { + require.TestingT + sdklog.TestingT +}) { var err error s.treeV2, err = NewCommitTree(s.dirV2, Options{ WriteWAL: true, @@ -213,7 +216,7 @@ func (s *SimMachine) openV2Tree(t require.TestingT) { ChangesetMaxTarget: 1, CompactAfterVersions: 0, ReaderUpdateInterval: 1, - }, sdklog.NewNopLogger()) + }, sdklog.NewTestLogger(t)) require.NoError(t, err, "failed to create iavlx tree") } @@ -242,11 +245,6 @@ func (s *SimMachine) GetN(t *rapid.T) { } } -func (s *SimMachine) CloseReopen(t *rapid.T) { - require.NoError(t, s.treeV2.Close(), "failed to close iavlx tree") - s.openV2Tree(t) -} - func (s *SimMachine) set(t *rapid.T) { // choose either a new or an existing key key := s.selectKey(t) @@ -338,11 +336,11 @@ func (s *SimMachine) Commit(t *rapid.T) { err = VerifyTree(s.treeV2) require.NoError(t, err, "failed to verify V2 tree") require.Equal(t, hash1, commitId2.Hash, "hash mismatch between V1 and V2 trees") - //closeReopen := rapid.Bool().Draw(t, "closeReopen") - //if closeReopen { - // require.NoError(t, s.treeV2.Close()) - // s.openV2Tree(t) - //} + closeReopen := rapid.Bool().Draw(t, "closeReopen") + if closeReopen { + require.NoError(t, s.treeV2.Close()) + s.openV2Tree(t) + } } func (s *SimMachine) debugDump(t *rapid.T) { From 27a0b64eba52936340523e71bb53427e961541d1 Mon Sep 17 00:00:00 2001 From: Tyler <48813565+technicallyty@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:17:35 -0500 Subject: [PATCH 84/87] renames, make sure to call cancel --- server/start.go | 3 ++- telemetry/README.md | 2 +- telemetry/config.go | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/server/start.go b/server/start.go index c64dbe19a445..0c321241c77b 100644 --- a/server/start.go +++ b/server/start.go @@ -609,7 +609,8 @@ func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions cleanupFn = func() { traceCleanupFn() - shutdownCtx, _ := context.WithTimeout(context.Background(), 5*time.Second) + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() if err := telemetry.Shutdown(shutdownCtx); err != nil { svrCtx.Logger.Error("failed to shutdown telemetry", "error", err) } diff --git a/telemetry/README.md b/telemetry/README.md index bbaabbe34c01..d1f873423b51 100644 --- a/telemetry/README.md +++ b/telemetry/README.md @@ -57,7 +57,7 @@ Developers need to do two things to use this package properly: If these steps are followed, developers can follow the official golang otel conventions of declaring package-level tracer and meter instances using otel.Tracer() and otel.Meter(). -NOTE: it is important to thread context.Context properly for spans, metrics and logs to be +NOTE: it is important to thread context.Context properly for spans, metrics, and logs to be correlated correctly. When using the SDK's context type, spans must be started with Context.StartSpan to get an SDK context which has the span set correctly. diff --git a/telemetry/config.go b/telemetry/config.go index ac16ee735aef..6dedddfeacd2 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -11,7 +11,7 @@ import ( "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/contrib/instrumentation/host" "go.opentelemetry.io/contrib/instrumentation/runtime" - "go.opentelemetry.io/contrib/otelconf/v0.3.0" + otelconf "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" @@ -29,13 +29,13 @@ var shutdownFuncs []func(context.Context) error var isTelemetryEnabled = true func init() { - err := doInit() + err := initOpenTelemetry() if err != nil { panic(err) } } -func doInit() error { +func initOpenTelemetry() error { var err error var opts []otelconf.ConfigurationOption From 6f89fda6b37c58479fcad4d9f7e9c935e416b4ec Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 24 Nov 2025 11:45:02 -0500 Subject: [PATCH 85/87] bug fixes related to orphan tracking, graphviz updates from presentation --- iavlx/cleanup.go | 14 ++-- iavlx/commit_tree.go | 19 ++++- iavlx/compactor.go | 18 +++-- iavlx/dot_graph.go | 162 ++++++++++++++++++++++++++++++++++++++++--- iavlx/options.go | 3 +- iavlx/orphans.go | 7 +- iavlx/tree_store.go | 11 +++ iavlx/verify.go | 2 +- 8 files changed, 212 insertions(+), 24 deletions(-) diff --git a/iavlx/cleanup.go b/iavlx/cleanup.go index 048dd75daac3..996e1f35dae1 100644 --- a/iavlx/cleanup.go +++ b/iavlx/cleanup.go @@ -52,6 +52,13 @@ func newCleanupProc(treeStore *TreeStore) *cleanupProc { func (cp *cleanupProc) run() { ctx, span := tracer.Start(context.Background(), "cleanupProc") defer span.End() + // before we shutdown save any pending orphans + defer func() { + err := cp.doMarkOrphans() + if err != nil { + cp.logger.Error("failed to mark orphans at shutdown", "error", err) + } + }() defer close(cp.cleanupProcDone) minCompactorInterval := time.Second * time.Duration(cp.opts.MinCompactionSeconds) @@ -239,10 +246,9 @@ func (cp *cleanupProc) processEntry(ctx context.Context, entry, nextEntry *chang savedVersion := cp.savedVersion.Load() retainVersions := cp.opts.RetainVersions retentionWindowBottom := savedVersion - retainVersions - - // Skip changesets within retention window - if cs.info.EndVersion >= retentionWindowBottom { - return nil + if retainVersions == 0 { + // retain everything + retentionWindowBottom = 0 } compactOrphanAge := cp.opts.GetCompactionOrphanAge() diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go index 3b0c24f88dcb..9c20c5c88b00 100644 --- a/iavlx/commit_tree.go +++ b/iavlx/commit_tree.go @@ -350,6 +350,17 @@ func (c *CommitTree) GetImmutable(version int64) (storetypes.KVStore, error) { return NewImmutableTree(rootPtr), nil } +func (c *CommitTree) ResolveRoot(version uint32) (*NodePointer, error) { + if version == 0 { + version = c.store.stagedVersion - 1 + } + return c.store.ResolveRoot(version) +} + +func (c *CommitTree) Version() uint32 { + return c.store.stagedVersion - 1 +} + func (c *CommitTree) Close() error { if c.walQueue != nil { c.walQueue.Close() @@ -382,9 +393,10 @@ func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []by } var leftHash, rightHash []byte + var id NodeID if memNode.IsLeaf() { ctx.leafNodeIdx++ - np.id = NewNodeID(true, uint64(ctx.version), ctx.leafNodeIdx) + id = NewNodeID(true, uint64(ctx.version), ctx.leafNodeIdx) } else { // post-order traversal leftHash, err = commitTraverse(ctx, memNode.left, depth+1) @@ -397,9 +409,10 @@ func commitTraverse(ctx *commitContext, np *NodePointer, depth uint8) (hash []by } ctx.branchNodeIdx++ - np.id = NewNodeID(false, uint64(ctx.version), ctx.branchNodeIdx) - + id = NewNodeID(false, uint64(ctx.version), ctx.branchNodeIdx) } + np.id = id + memNode.nodeId = id if memNode.hash != nil { // hash previously computed node diff --git a/iavlx/compactor.go b/iavlx/compactor.go index aa1c2ad109b6..da90a3e09e34 100644 --- a/iavlx/compactor.go +++ b/iavlx/compactor.go @@ -28,6 +28,7 @@ type Compactor struct { branchesWriter *StructWriter[BranchLayout] versionsWriter *StructWriter[VersionInfo] kvlogWriter *KVLogWriter + orphanWriter *OrphanWriter keyCache map[string]uint32 // offsetCache holds the updated 1-based offsets of nodes affected by compacting. @@ -40,7 +41,6 @@ type Compactor struct { leafOrphanVersionTotal uint64 branchOrphanVersionTotal uint64 ctx context.Context - retainedOrphans map[NodeID]uint32 } func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, store *TreeStore) (*Compactor, error) { @@ -84,9 +84,9 @@ func NewCompacter(ctx context.Context, reader *Changeset, opts CompactOptions, s leavesWriter: NewStructWriter[LeafLayout](newFiles.leavesFile), branchesWriter: NewStructWriter[BranchLayout](newFiles.branchesFile), versionsWriter: NewStructWriter[VersionInfo](newFiles.versionsFile), + orphanWriter: NewOrphanWriter(newFiles.orphansFile), keyCache: make(map[string]uint32), offsetCache: make(map[NodeID]uint32), - retainedOrphans: make(map[NodeID]uint32), } // Process first changeset immediately @@ -176,7 +176,11 @@ func (c *Compactor) processChangeset(reader *Changeset) error { c.offsetCache[id] = uint32(c.leavesWriter.Count()) - c.retainedOrphans[id] = orphanVersion + if orphanVersion != 0 { + if err := c.orphanWriter.WriteOrphan(orphanVersion, id); err != nil { + return fmt.Errorf("failed to write retained orphan leaf %s: %w", id, err) + } + } } newBranchStartIdx := uint32(0) @@ -236,7 +240,11 @@ func (c *Compactor) processChangeset(reader *Changeset) error { } c.offsetCache[id] = uint32(c.branchesWriter.Count()) - c.retainedOrphans[id] = orphanVersion + if orphanVersion != 0 { + if err := c.orphanWriter.WriteOrphan(orphanVersion, id); err != nil { + return fmt.Errorf("failed to write retained orphan leaf %s: %w", id, err) + } + } } verInfo = VersionInfo{ @@ -293,6 +301,7 @@ func (c *Compactor) Seal() (*Changeset, error) { c.leavesWriter.Flush(), c.branchesWriter.Flush(), c.versionsWriter.Flush(), + c.orphanWriter.Flush(), c.files.RewriteInfo(), } if c.kvlogWriter != nil { @@ -312,7 +321,6 @@ func (c *Compactor) Seal() (*Changeset, error) { } // write orphan map - err = cs.orphanWriter.WriteOrphanMap(c.retainedOrphans) if err != nil { return nil, fmt.Errorf("failed to write orphan map during compaction seal: %w", err) } diff --git a/iavlx/dot_graph.go b/iavlx/dot_graph.go index c5043b0c19d7..3b0086e278be 100644 --- a/iavlx/dot_graph.go +++ b/iavlx/dot_graph.go @@ -5,7 +5,7 @@ import ( "io" ) -func DebugTraverse(nodePtr *NodePointer, onNode func(node, parent Node, direction string) error) error { +func DebugTraverseNode(nodePtr *NodePointer, onNode func(node, parent Node, direction string) error) error { if nodePtr == nil { return nil } @@ -39,8 +39,11 @@ func DebugTraverse(nodePtr *NodePointer, onNode func(node, parent Node, directio return traverse(nodePtr, nil, "") } -func RenderDotGraph(writer io.Writer, nodePtr *NodePointer) error { - _, err := fmt.Fprintln(writer, "digraph G {") +var graphvizFillColors = []string{"purple", "green", "red", "blue", "yellow"} +var graphvizTextColors = []string{"white", "black", "white", "white", "black"} + +func RenderNodeDotGraph(writer io.Writer, nodePtr *NodePointer) error { + _, err := fmt.Fprintln(writer, "digraph G {\n\trankdir=BT") if err != nil { return err } @@ -52,15 +55,21 @@ func RenderDotGraph(writer io.Writer, nodePtr *NodePointer) error { return finishGraph() } - err = DebugTraverse(nodePtr, func(node, parent Node, direction string) error { + err = DebugTraverseNode(nodePtr, func(node, parent Node, direction string) error { key, err := node.Key() if err != nil { return err } version := node.Version() + id := node.ID() - label := fmt.Sprintf("ver: %d key:0x%x ", version, key) + idx := id.Index() + label := fmt.Sprintf("ver: %d idx: %d key:0x%x ", version, idx, key) + attrs := "" + fillColor := graphvizFillColors[version%uint32(len(graphvizFillColors))] + textColor := graphvizTextColors[version%uint32(len(graphvizTextColors))] + attrs += fmt.Sprintf(" fillcolor=%s fontcolor=%s style=filled", fillColor, textColor) if node.IsLeaf() { value, err := node.Value() if err != nil { @@ -68,19 +77,20 @@ func RenderDotGraph(writer io.Writer, nodePtr *NodePointer) error { } label += fmt.Sprintf("val:0x%X", value) + attrs += " shape=box" } else { label += fmt.Sprintf("ht:%d sz:%d", node.Height(), node.Size()) } - nodeName := fmt.Sprintf("n%p", node) + nodeName := graphvizNodeID(id) - _, err = fmt.Fprintf(writer, "%s [label=\"%s\"];\n", nodeName, label) + _, err = fmt.Fprintf(writer, "\t%s [id=%s label=\"%s\"%s];\n", nodeName, nodeName, label, attrs) if err != nil { return err } if parent != nil { - parentName := fmt.Sprintf("n%p", parent) - _, err = fmt.Fprintf(writer, "%s -> %s [label=\"%s\"];\n", parentName, nodeName, direction) + parentName := graphvizNodeID(parent.ID()) + _, err = fmt.Fprintf(writer, "\t%s -> %s [label=\"%s\"];\n", parentName, nodeName, direction) if err != nil { return err } @@ -93,3 +103,137 @@ func RenderDotGraph(writer io.Writer, nodePtr *NodePointer) error { return finishGraph() } + +func RenderChangesetDotGraph(writer io.Writer, cs *Changeset, orphans map[NodeID]uint32) error { + _, err := fmt.Fprintln(writer, "digraph G {\n\trankdir=LR") + if err != nil { + return err + } + finishGraph := func() error { + _, err := fmt.Fprintln(writer, "}") + return err + } + + _, err = fmt.Fprintln(writer, "\tsubgraph cluster_branches {\t\nlabel=\"Branches\"") + if err != nil { + return err + } + + numBranches := cs.branchesData.Count() + curVersion := uint64(0) + var lastBranchId NodeID + for i := 0; i < numBranches; i++ { + branchLayout := cs.branchesData.UnsafeItem(uint32(i)) + id := branchLayout.ID() + nodeVersion := id.Version() + if nodeVersion != curVersion { + if curVersion != 0 { + _, err = fmt.Fprintln(writer, "\t}") + } + fillColor := graphvizFillColors[nodeVersion%uint64(len(graphvizFillColors))] + textColor := graphvizTextColors[nodeVersion%uint64(len(graphvizTextColors))] + _, err = fmt.Fprintf(writer, "\tsubgraph cluster_B%d {\n\t\tlabel=\"Version %d\" color=%s style=filled fontcolor=%s node [fontcolor=%s]\n", nodeVersion, nodeVersion, fillColor, textColor, textColor) + } + curVersion = nodeVersion + if lastBranchId != 0 { + _, err = fmt.Fprintf(writer, "\t\t%s -> %s [style=invis];\n", graphvizNodeID(lastBranchId), graphvizNodeID(id)) + } + lastBranchId = id + + nodeName := graphvizNodeID(id) + idx := id.Index() + label := fmt.Sprintf("idx: %d", idx) + orphanVersion, isOrphan := orphans[id] + if isOrphan { + label += fmt.Sprintf("
orphaned: %d", orphanVersion) + } + attrs := "" + if isOrphan { + attrs = " style=dashed" + } + vi, err := cs.getVersionInfo(uint32(nodeVersion)) + if err != nil { + return err + } + if vi.RootID == id { + attrs += " shape=doublecircle" + } + _, err = fmt.Fprintf(writer, "\t\t%s [id=%s label=<%s>%s];\n", nodeName, nodeName, label, attrs) + if err != nil { + return err + } + } + // finish last version subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + // finish branches subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + _, err = fmt.Fprintln(writer, "\tsubgraph cluster_leaves {\t\nlabel=\"Leaves\"") + if err != nil { + return err + } + numLeaves := cs.leavesData.Count() + curVersion = 0 + var lastLeafId NodeID + for i := 0; i < numLeaves; i++ { + leafLayout := cs.leavesData.UnsafeItem(uint32(i)) + id := leafLayout.ID() + nodeVersion := id.Version() + if nodeVersion != curVersion { + if curVersion != 0 { + _, err = fmt.Fprintln(writer, "\t}") + } + fillColor := graphvizFillColors[nodeVersion%uint64(len(graphvizFillColors))] + textColor := graphvizTextColors[nodeVersion%uint64(len(graphvizTextColors))] + _, err = fmt.Fprintf(writer, "\tsubgraph cluster_L%d {\n\t\tlabel=\"Version %d\" color=%s fontcolor=%s style=filled node [fontcolor=%s]\n", nodeVersion, nodeVersion, fillColor, textColor, textColor) + } + curVersion = nodeVersion + if lastLeafId != 0 { + _, err = fmt.Fprintf(writer, "\t\t%s -> %s [style=invis];\n", graphvizNodeID(lastLeafId), graphvizNodeID(id)) + } + lastLeafId = id + + nodeName := graphvizNodeID(id) + label := fmt.Sprintf("idx: %d", id.Index()) + orphanVersion, isOrphan := orphans[id] + if isOrphan { + label += fmt.Sprintf("
orphaned: %d", orphanVersion) + } + attrs := "" + if isOrphan { + attrs = " style=dashed" + } + _, err = fmt.Fprintf(writer, "\t\t%s [id=%s label=<%s> shape=box%s];\n", nodeName, nodeName, label, attrs) + if err != nil { + return err + } + } + // finish last version subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + // finish leaves subgraph + _, err = fmt.Fprintln(writer, "\t}") + if err != nil { + return err + } + + return finishGraph() +} + +func graphvizNodeID(node NodeID) string { + if node.IsLeaf() { + return fmt.Sprintf("L%d_%d", node.Version(), node.Index()) + } else { + return fmt.Sprintf("B%d_%d", node.Version(), node.Index()) + } +} diff --git a/iavlx/options.go b/iavlx/options.go index 9aef5f93f150..ff1dfb174863 100644 --- a/iavlx/options.go +++ b/iavlx/options.go @@ -18,7 +18,8 @@ type Options struct { // CompactionOrphanAge is the average age of orphans (in versions) at which compaction is triggered CompactionOrphanAge uint32 `json:"compaction_orphan_age"` - // RetainVersions is the number of recent versions to keep uncompacted + // RetainVersions is the number of recent versions to keep uncompacted. + // If this is set to 0, all versions will be retained, and the compactor will only join changesets without removing any. RetainVersions uint32 `json:"retain_versions"` // MinCompactionSeconds is the minimum interval between compaction runs MinCompactionSeconds uint32 `json:"min_compaction_seconds"` diff --git a/iavlx/orphans.go b/iavlx/orphans.go index dc5b9df4fb97..e2cb2395753f 100644 --- a/iavlx/orphans.go +++ b/iavlx/orphans.go @@ -3,6 +3,7 @@ package iavlx import ( "bufio" "encoding/binary" + "fmt" "io" "os" ) @@ -36,8 +37,12 @@ func (ow *OrphanWriter) WriteOrphanMap(orphanMap map[NodeID]uint32) error { } func ReadOrphanMap(file *os.File) (map[NodeID]uint32, error) { + file2, err := os.Open(file.Name()) + if err != nil { + return nil, fmt.Errorf("failed to open orphan file for reading: %w", err) + } orphanMap := make(map[NodeID]uint32) - rdr := bufio.NewReader(file) + rdr := bufio.NewReader(file2) var buf [12]byte for { _, err := rdr.Read(buf[:]) diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go index aa650e9931d8..c4bf4cf0f946 100644 --- a/iavlx/tree_store.go +++ b/iavlx/tree_store.go @@ -352,6 +352,17 @@ func (ts *TreeStore) syncProc() { } func (ts *TreeStore) Close() error { + // save the current writer if it has uncommitted data + startVersion := ts.currentWriter.files.info.StartVersion + if startVersion != 0 { + cs, err := ts.currentWriter.Seal() + if err != nil { + return fmt.Errorf("failed to seal current changeset on close: %w", err) + } + ts.setActiveReader(startVersion, cs) + ts.savedVersion.Store(ts.currentWriter.files.info.EndVersion) + } + ts.cleanupProc.shutdown() if ts.syncQueue != nil { diff --git a/iavlx/verify.go b/iavlx/verify.go index 4718e7441ff1..25e012d75184 100644 --- a/iavlx/verify.go +++ b/iavlx/verify.go @@ -37,7 +37,7 @@ var _ error = &DebugError{} // return err // } else { // buf := &bytes.Buffer{} -// err2 := RenderDotGraph(buf, np) +// err2 := RenderNodeDotGraph(buf, np) // if err2 == nil { // err = &DebugError{ // Graph: buf.String(), From 971f23276ab4f91f5b58a907aeabc39e07a6f369 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 24 Nov 2025 11:54:59 -0500 Subject: [PATCH 86/87] bug fixes related to symlinks from visualize branch --- iavlx/changeset_files.go | 16 +++++++++++++--- iavlx/tree_test.go | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/iavlx/changeset_files.go b/iavlx/changeset_files.go index 81fbc4c6957d..14262f0a6ac1 100644 --- a/iavlx/changeset_files.go +++ b/iavlx/changeset_files.go @@ -56,7 +56,14 @@ func CreateChangesetFiles(treeDir string, startVersion, compactedAt uint32, kvlo localKVLogPath := filepath.Join(dir, "kv.log") if kvlogPath == "" { - kvlogPath = localKVLogPath + // For original (non-compacted) changesets, normalize the path by evaluating + // symlinks in the directory path to ensure consistent comparisons later. + // This handles platform differences like /var vs /private/var on macOS. + normalizedDir, err := filepath.EvalSymlinks(dir) + if err != nil { + return nil, fmt.Errorf("failed to eval directory path: %w", err) + } + kvlogPath = filepath.Join(normalizedDir, "kv.log") } else { // create symlink to kvlog so that it can be reopened later err := os.Symlink(kvlogPath, localKVLogPath) @@ -267,8 +274,11 @@ func (cr *ChangesetFiles) DeleteFiles(args ChangesetDeleteArgs) error { os.Remove(cr.versionsFile.Name()), os.Remove(cr.orphansFile.Name()), } - if cr.kvlogFile.Name() != args.SaveKVLogPath { - errs = append(errs, os.Remove(cr.kvlogFile.Name())) + + localKVLogPath := filepath.Join(cr.dir, "kv.log") + if cr.kvlogPath != args.SaveKVLogPath { + // delete the local kv.log (which might be a symlink) + errs = append(errs, os.Remove(localKVLogPath)) } err := errors.Join(errs...) if err != nil { diff --git a/iavlx/tree_test.go b/iavlx/tree_test.go index 17e6e16a176b..4de85fb91d83 100644 --- a/iavlx/tree_test.go +++ b/iavlx/tree_test.go @@ -144,7 +144,7 @@ func renderTree(t interface { }, tree *ImmutableTree, ) { graph := &bytes.Buffer{} - require.NoError(t, RenderDotGraph(graph, tree.root)) + require.NoError(t, RenderNodeDotGraph(graph, tree.root)) t.Logf("tree graph:\n%s", graph.String()) } From 19407bad405b61e802531fc97ae18354376512df Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Tue, 2 Dec 2025 12:59:30 -0500 Subject: [PATCH 87/87] switch to slog logger --- iavlx/commit_multi_tree.go | 7 +++---- iavlx/commit_tree.go | 7 +++---- iavlx/tree_store.go | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/iavlx/commit_multi_tree.go b/iavlx/commit_multi_tree.go index a41fbc259de0..e00735d9610b 100644 --- a/iavlx/commit_multi_tree.go +++ b/iavlx/commit_multi_tree.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" io "io" + "log/slog" "os" "path/filepath" "sync" @@ -11,8 +12,6 @@ import ( dbm "github.com/cosmos/cosmos-db" protoio "github.com/cosmos/gogoproto/io" - "cosmossdk.io/log" - "cosmossdk.io/store/mem" "cosmossdk.io/store/metrics" pruningtypes "cosmossdk.io/store/pruning/types" @@ -24,7 +23,7 @@ import ( type CommitMultiTree struct { dir string opts Options - logger log.Logger + logger *slog.Logger trees []storetypes.CommitStore // always ordered by tree name treeKeys []storetypes.StoreKey // always ordered by tree name storeTypes []storetypes.StoreType // store types by tree index @@ -378,7 +377,7 @@ func (db *CommitMultiTree) SetMetrics(metrics metrics.StoreMetrics) { db.logger.Warn("SetMetrics is not implemented for CommitMultiTree") } -func LoadDB(path string, opts *Options, logger log.Logger) (*CommitMultiTree, error) { +func LoadDB(path string, opts *Options, logger *slog.Logger) (*CommitMultiTree, error) { // n := len(treeNames) //trees := make([]*CommitTree, n) //treesByName := make(map[string]int, n) diff --git a/iavlx/commit_tree.go b/iavlx/commit_tree.go index 9c20c5c88b00..d19bc4d33721 100644 --- a/iavlx/commit_tree.go +++ b/iavlx/commit_tree.go @@ -3,11 +3,10 @@ package iavlx import ( "fmt" "io" + "log/slog" "sync" "sync/atomic" - "cosmossdk.io/log" - pruningtypes "cosmossdk.io/store/pruning/types" storetypes "cosmossdk.io/store/types" ) @@ -29,13 +28,13 @@ type CommitTree struct { pendingOrphans [][]NodeID - logger log.Logger + logger *slog.Logger lastCommitId storetypes.CommitID commitCtx *commitContext } -func NewCommitTree(dir string, opts Options, logger log.Logger) (*CommitTree, error) { +func NewCommitTree(dir string, opts Options, logger *slog.Logger) (*CommitTree, error) { ts, err := NewTreeStore(dir, opts, logger) if err != nil { return nil, fmt.Errorf("failed to create tree store: %w", err) diff --git a/iavlx/tree_store.go b/iavlx/tree_store.go index c4bf4cf0f946..a22f8408126f 100644 --- a/iavlx/tree_store.go +++ b/iavlx/tree_store.go @@ -3,17 +3,16 @@ package iavlx import ( "errors" "fmt" + "log/slog" "sync" "sync/atomic" "time" "github.com/tidwall/btree" - - "cosmossdk.io/log" ) type TreeStore struct { - logger log.Logger + logger *slog.Logger dir string currentWriter *ChangesetWriter @@ -40,7 +39,7 @@ type changesetEntry struct { changeset atomic.Pointer[Changeset] } -func NewTreeStore(dir string, options Options, logger log.Logger) (*TreeStore, error) { +func NewTreeStore(dir string, options Options, logger *slog.Logger) (*TreeStore, error) { ts := &TreeStore{ dir: dir, changesets: &btree.Map[uint32, *changesetEntry]{},