mirror of
https://github.com/ipfs/kubo.git
synced 2025-07-01 02:30:39 +08:00
Merge pull request #509 from jbenet/ipfs-object-stat
ipfs object learned stat
This commit is contained in:
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
@ -36,6 +37,7 @@ ipfs object get <key> - Get the DAG node named by <key>
|
||||
ipfs object put <data> <encoding> - Stores input, outputs its key
|
||||
ipfs object data <key> - Outputs raw bytes in an object
|
||||
ipfs object links <key> - Outputs links pointed to by object
|
||||
ipfs object stat <key> - Outputs statistics of object
|
||||
`,
|
||||
},
|
||||
|
||||
@ -44,6 +46,7 @@ ipfs object links <key> - Outputs links pointed to by object
|
||||
"links": objectLinksCmd,
|
||||
"get": objectGetCmd,
|
||||
"put": objectPutCmd,
|
||||
"stat": objectStatCmd,
|
||||
},
|
||||
}
|
||||
|
||||
@ -180,6 +183,64 @@ This command outputs data in the following encodings:
|
||||
},
|
||||
}
|
||||
|
||||
var objectStatCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Get stats for the DAG node named by <key>",
|
||||
ShortDescription: `
|
||||
'ipfs object stat' is a plumbing command to print DAG node statistics.
|
||||
<key> is a base58 encoded multihash. It outputs to stdout:
|
||||
|
||||
NumLinks int number of links in link table
|
||||
BlockSize int size of the raw, encoded data
|
||||
LinksSize int size of the links segment
|
||||
DataSize int size of the data segment
|
||||
CumulativeSize int cumulative size of object and its references
|
||||
`,
|
||||
},
|
||||
|
||||
Arguments: []cmds.Argument{
|
||||
cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)"),
|
||||
},
|
||||
Run: func(req cmds.Request) (interface{}, error) {
|
||||
n, err := req.Context().GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := req.Arguments()[0]
|
||||
|
||||
object, err := objectGet(n, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ns, err := object.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
},
|
||||
Type: dag.NodeStat{},
|
||||
Marshalers: cmds.MarshalerMap{
|
||||
cmds.Text: func(res cmds.Response) (io.Reader, error) {
|
||||
ns := res.Output().(dag.NodeStat)
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := func(s string, n int) {
|
||||
buf.Write([]byte(fmt.Sprintf("%s: %d\n", s, n)))
|
||||
}
|
||||
w("NumLinks", ns.NumLinks)
|
||||
w("BlockSize", ns.BlockSize)
|
||||
w("LinksSize", ns.LinksSize)
|
||||
w("DataSize", ns.DataSize)
|
||||
w("CumulativeSize", ns.CumulativeSize)
|
||||
|
||||
return &buf, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var objectPutCmd = &cmds.Command{
|
||||
Helptext: cmds.HelpText{
|
||||
Tagline: "Stores input as a DAG object, outputs its key",
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
|
||||
|
||||
mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
|
||||
blocks "github.com/jbenet/go-ipfs/blocks"
|
||||
bserv "github.com/jbenet/go-ipfs/blockservice"
|
||||
u "github.com/jbenet/go-ipfs/util"
|
||||
@ -18,11 +17,6 @@ import (
|
||||
var log = u.Logger("merkledag")
|
||||
var ErrNotFound = fmt.Errorf("merkledag: not found")
|
||||
|
||||
// NodeMap maps u.Keys to Nodes.
|
||||
// We cannot use []byte/Multihash for keys :(
|
||||
// so have to convert Multihash bytes to string (u.Key)
|
||||
type NodeMap map[u.Key]*Node
|
||||
|
||||
// DAGService is an IPFS Merkle DAG service.
|
||||
type DAGService interface {
|
||||
Add(*Node) (u.Key, error)
|
||||
@ -39,146 +33,6 @@ func NewDAGService(bs *bserv.BlockService) DAGService {
|
||||
return &dagService{bs}
|
||||
}
|
||||
|
||||
// Node represents a node in the IPFS Merkle DAG.
|
||||
// nodes have opaque data and a set of navigable links.
|
||||
type Node struct {
|
||||
Links []*Link
|
||||
Data []byte
|
||||
|
||||
// cache encoded/marshaled value
|
||||
encoded []byte
|
||||
|
||||
cached mh.Multihash
|
||||
}
|
||||
|
||||
// Link represents an IPFS Merkle DAG Link between Nodes.
|
||||
type Link struct {
|
||||
// utf string name. should be unique per object
|
||||
Name string // utf8
|
||||
|
||||
// cumulative size of target object
|
||||
Size uint64
|
||||
|
||||
// multihash of the target object
|
||||
Hash mh.Multihash
|
||||
|
||||
// a ptr to the actual node for graph manipulation
|
||||
Node *Node
|
||||
}
|
||||
|
||||
type LinkSlice []*Link
|
||||
|
||||
func (ls LinkSlice) Len() int { return len(ls) }
|
||||
func (ls LinkSlice) Swap(a, b int) { ls[a], ls[b] = ls[b], ls[a] }
|
||||
func (ls LinkSlice) Less(a, b int) bool { return ls[a].Name < ls[b].Name }
|
||||
|
||||
// MakeLink creates a link to the given node
|
||||
func MakeLink(n *Node) (*Link, error) {
|
||||
s, err := n.Size()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := n.Multihash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Link{
|
||||
Size: s,
|
||||
Hash: h,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetNode returns the MDAG Node that this link points to
|
||||
func (l *Link) GetNode(serv DAGService) (*Node, error) {
|
||||
if l.Node != nil {
|
||||
return l.Node, nil
|
||||
}
|
||||
|
||||
return serv.Get(u.Key(l.Hash))
|
||||
}
|
||||
|
||||
// AddNodeLink adds a link to another node.
|
||||
func (n *Node) AddNodeLink(name string, that *Node) error {
|
||||
lnk, err := MakeLink(that)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lnk.Name = name
|
||||
lnk.Node = that
|
||||
|
||||
n.Links = append(n.Links, lnk)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNodeLink adds a link to another node. without keeping a reference to
|
||||
// the child node
|
||||
func (n *Node) AddNodeLinkClean(name string, that *Node) error {
|
||||
lnk, err := MakeLink(that)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lnk.Name = name
|
||||
|
||||
n.Links = append(n.Links, lnk)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove a link on this node by the given name
|
||||
func (n *Node) RemoveNodeLink(name string) error {
|
||||
for i, l := range n.Links {
|
||||
if l.Name == name {
|
||||
n.Links = append(n.Links[:i], n.Links[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// Copy returns a copy of the node.
|
||||
// NOTE: does not make copies of Node objects in the links.
|
||||
func (n *Node) Copy() *Node {
|
||||
nnode := new(Node)
|
||||
nnode.Data = make([]byte, len(n.Data))
|
||||
copy(nnode.Data, n.Data)
|
||||
|
||||
nnode.Links = make([]*Link, len(n.Links))
|
||||
copy(nnode.Links, n.Links)
|
||||
return nnode
|
||||
}
|
||||
|
||||
// Size returns the total size of the data addressed by node,
|
||||
// including the total sizes of references.
|
||||
func (n *Node) Size() (uint64, error) {
|
||||
b, err := n.Encoded(false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
s := uint64(len(b))
|
||||
for _, l := range n.Links {
|
||||
s += l.Size
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Multihash hashes the encoded data of this node.
|
||||
func (n *Node) Multihash() (mh.Multihash, error) {
|
||||
// Note: Encoded generates the hash and puts it in n.cached.
|
||||
_, err := n.Encoded(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return n.cached, nil
|
||||
}
|
||||
|
||||
// Key returns the Multihash as a key, for maps.
|
||||
func (n *Node) Key() (u.Key, error) {
|
||||
h, err := n.Multihash()
|
||||
return u.Key(h), err
|
||||
}
|
||||
|
||||
// dagService is an IPFS Merkle DAG service.
|
||||
// - the root is virtual (like a forest)
|
||||
// - stores nodes' data in a BlockService
|
||||
|
@ -85,6 +85,8 @@ func TestNode(t *testing.T) {
|
||||
} else {
|
||||
fmt.Println("key: ", k)
|
||||
}
|
||||
|
||||
SubtestNodeStat(t, n)
|
||||
}
|
||||
|
||||
printn("beep", n1)
|
||||
@ -92,6 +94,40 @@ func TestNode(t *testing.T) {
|
||||
printn("beep boop", n3)
|
||||
}
|
||||
|
||||
func SubtestNodeStat(t *testing.T, n *Node) {
|
||||
enc, err := n.Encoded(true)
|
||||
if err != nil {
|
||||
t.Error("n.Encoded(true) failed")
|
||||
return
|
||||
}
|
||||
|
||||
cumSize, err := n.Size()
|
||||
if err != nil {
|
||||
t.Error("n.Size() failed")
|
||||
return
|
||||
}
|
||||
|
||||
expected := NodeStat{
|
||||
NumLinks: len(n.Links),
|
||||
BlockSize: len(enc),
|
||||
LinksSize: len(enc) - len(n.Data), // includes framing.
|
||||
DataSize: len(n.Data),
|
||||
CumulativeSize: int(cumSize),
|
||||
}
|
||||
|
||||
actual, err := n.Stat()
|
||||
if err != nil {
|
||||
t.Error("n.Stat() failed")
|
||||
return
|
||||
}
|
||||
|
||||
if expected != actual {
|
||||
t.Error("n.Stat incorrect.\nexpect: %s\nactual: %s", expected, actual)
|
||||
} else {
|
||||
fmt.Printf("n.Stat correct: %s\n", actual)
|
||||
}
|
||||
}
|
||||
|
||||
type devZero struct{}
|
||||
|
||||
func (_ devZero) Read(b []byte) (int, error) {
|
||||
|
188
merkledag/node.go
Normal file
188
merkledag/node.go
Normal file
@ -0,0 +1,188 @@
|
||||
package merkledag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
|
||||
u "github.com/jbenet/go-ipfs/util"
|
||||
)
|
||||
|
||||
// NodeMap maps u.Keys to Nodes.
|
||||
// We cannot use []byte/Multihash for keys :(
|
||||
// so have to convert Multihash bytes to string (u.Key)
|
||||
type NodeMap map[u.Key]*Node
|
||||
|
||||
// Node represents a node in the IPFS Merkle DAG.
|
||||
// nodes have opaque data and a set of navigable links.
|
||||
type Node struct {
|
||||
Links []*Link
|
||||
Data []byte
|
||||
|
||||
// cache encoded/marshaled value
|
||||
encoded []byte
|
||||
|
||||
cached mh.Multihash
|
||||
}
|
||||
|
||||
// NodeStat is a statistics object for a Node. Mostly sizes.
|
||||
type NodeStat struct {
|
||||
NumLinks int // number of links in link table
|
||||
BlockSize int // size of the raw, encoded data
|
||||
LinksSize int // size of the links segment
|
||||
DataSize int // size of the data segment
|
||||
CumulativeSize int // cumulative size of object and its references
|
||||
}
|
||||
|
||||
func (ns NodeStat) String() string {
|
||||
f := "NodeStat{NumLinks: %d, BlockSize: %d, LinksSize: %d, DataSize: %d, CumulativeSize: %d}"
|
||||
return fmt.Sprintf(f, ns.NumLinks, ns.BlockSize, ns.LinksSize, ns.DataSize, ns.CumulativeSize)
|
||||
}
|
||||
|
||||
// Link represents an IPFS Merkle DAG Link between Nodes.
|
||||
type Link struct {
|
||||
// utf string name. should be unique per object
|
||||
Name string // utf8
|
||||
|
||||
// cumulative size of target object
|
||||
Size uint64
|
||||
|
||||
// multihash of the target object
|
||||
Hash mh.Multihash
|
||||
|
||||
// a ptr to the actual node for graph manipulation
|
||||
Node *Node
|
||||
}
|
||||
|
||||
type LinkSlice []*Link
|
||||
|
||||
func (ls LinkSlice) Len() int { return len(ls) }
|
||||
func (ls LinkSlice) Swap(a, b int) { ls[a], ls[b] = ls[b], ls[a] }
|
||||
func (ls LinkSlice) Less(a, b int) bool { return ls[a].Name < ls[b].Name }
|
||||
|
||||
// MakeLink creates a link to the given node
|
||||
func MakeLink(n *Node) (*Link, error) {
|
||||
s, err := n.Size()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := n.Multihash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Link{
|
||||
Size: s,
|
||||
Hash: h,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetNode returns the MDAG Node that this link points to
|
||||
func (l *Link) GetNode(serv DAGService) (*Node, error) {
|
||||
if l.Node != nil {
|
||||
return l.Node, nil
|
||||
}
|
||||
|
||||
return serv.Get(u.Key(l.Hash))
|
||||
}
|
||||
|
||||
// AddNodeLink adds a link to another node.
|
||||
func (n *Node) AddNodeLink(name string, that *Node) error {
|
||||
lnk, err := MakeLink(that)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lnk.Name = name
|
||||
lnk.Node = that
|
||||
|
||||
n.Links = append(n.Links, lnk)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNodeLink adds a link to another node. without keeping a reference to
|
||||
// the child node
|
||||
func (n *Node) AddNodeLinkClean(name string, that *Node) error {
|
||||
lnk, err := MakeLink(that)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lnk.Name = name
|
||||
|
||||
n.Links = append(n.Links, lnk)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove a link on this node by the given name
|
||||
func (n *Node) RemoveNodeLink(name string) error {
|
||||
for i, l := range n.Links {
|
||||
if l.Name == name {
|
||||
n.Links = append(n.Links[:i], n.Links[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// Copy returns a copy of the node.
|
||||
// NOTE: does not make copies of Node objects in the links.
|
||||
func (n *Node) Copy() *Node {
|
||||
nnode := new(Node)
|
||||
nnode.Data = make([]byte, len(n.Data))
|
||||
copy(nnode.Data, n.Data)
|
||||
|
||||
nnode.Links = make([]*Link, len(n.Links))
|
||||
copy(nnode.Links, n.Links)
|
||||
return nnode
|
||||
}
|
||||
|
||||
// Size returns the total size of the data addressed by node,
|
||||
// including the total sizes of references.
|
||||
func (n *Node) Size() (uint64, error) {
|
||||
b, err := n.Encoded(false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
s := uint64(len(b))
|
||||
for _, l := range n.Links {
|
||||
s += l.Size
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Stat returns statistics on the node.
|
||||
func (n *Node) Stat() (NodeStat, error) {
|
||||
enc, err := n.Encoded(false)
|
||||
if err != nil {
|
||||
return NodeStat{}, err
|
||||
}
|
||||
|
||||
cumSize, err := n.Size()
|
||||
if err != nil {
|
||||
return NodeStat{}, err
|
||||
}
|
||||
|
||||
return NodeStat{
|
||||
NumLinks: len(n.Links),
|
||||
BlockSize: len(enc),
|
||||
LinksSize: len(enc) - len(n.Data), // includes framing.
|
||||
DataSize: len(n.Data),
|
||||
CumulativeSize: int(cumSize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Multihash hashes the encoded data of this node.
|
||||
func (n *Node) Multihash() (mh.Multihash, error) {
|
||||
// Note: Encoded generates the hash and puts it in n.cached.
|
||||
_, err := n.Encoded(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return n.cached, nil
|
||||
}
|
||||
|
||||
// Key returns the Multihash as a key, for maps.
|
||||
func (n *Node) Key() (u.Key, error) {
|
||||
h, err := n.Multihash()
|
||||
return u.Key(h), err
|
||||
}
|
Reference in New Issue
Block a user