swarm/storage: complete pyramid splitter

* add size calculation, chunk passing and waitgroup control
* benchmark against tree chunker - conclusions remain
* remove logging from chunker
* fix joiner and extend tests and benchmarks
This commit is contained in:
zelig 2016-07-15 14:46:45 +02:00
parent 5097da7704
commit ce2a7e23e6
5 changed files with 198 additions and 128 deletions

View file

@ -7,9 +7,8 @@ import (
"hash" "hash"
"io" "io"
"sync" "sync"
// "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger" // "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/logger/glog"
) )
/* /*
@ -321,7 +320,7 @@ func (self *LazyChunkReader) ReadAt(b []byte, off int64) (read int, err error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
glog.V(logger.Detail).Infof("readAt: len(b): %v, off: %v, size: %v ", len(b), off, size) // glog.V(logger.Detail).Infof("readAt: len(b): %v, off: %v, size: %v ", len(b), off, size)
errC := make(chan error) errC := make(chan error)
// glog.V(logger.Detail).Infof("[BZZ] readAt: reading %v into %d bytes at offset %d.", self.chunk.Key.Log(), len(b), off) // glog.V(logger.Detail).Infof("[BZZ] readAt: reading %v into %d bytes at offset %d.", self.chunk.Key.Log(), len(b), off)
@ -350,9 +349,9 @@ func (self *LazyChunkReader) ReadAt(b []byte, off int64) (read int, err error) {
return 0, err return 0, err
} }
// glog.V(logger.Detail).Infof("[BZZ] ReadAt received %v", err) // glog.V(logger.Detail).Infof("[BZZ] ReadAt received %v", err)
glog.V(logger.Detail).Infof("end: len(b): %v, off: %v, size: %v ", len(b), off, size) // glog.V(logger.Detail).Infof("end: len(b): %v, off: %v, size: %v ", len(b), off, size)
if off+int64(len(b)) >= size { if off+int64(len(b)) >= size {
glog.V(logger.Detail).Infof(" len(b): %v EOF", len(b)) // glog.V(logger.Detail).Infof(" len(b): %v EOF", len(b))
return len(b), io.EOF return len(b), io.EOF
} }
// glog.V(logger.Detail).Infof("[BZZ] ReadAt returning at %d: %v", read, err) // glog.V(logger.Detail).Infof("[BZZ] ReadAt returning at %d: %v", read, err)
@ -362,7 +361,7 @@ func (self *LazyChunkReader) ReadAt(b []byte, off int64) (read int, err error) {
func (self *LazyChunkReader) join(b []byte, off int64, eoff int64, depth int, treeSize int64, chunk *Chunk, parentWg *sync.WaitGroup, errC chan error, quitC chan bool) { func (self *LazyChunkReader) join(b []byte, off int64, eoff int64, depth int, treeSize int64, chunk *Chunk, parentWg *sync.WaitGroup, errC chan error, quitC chan bool) {
defer parentWg.Done() defer parentWg.Done()
// return NewDPA(&LocalStore{}) // return NewDPA(&LocalStore{})
glog.V(logger.Detail).Infof("inh len(b): %v, off: %v eoff: %v ", len(b), off, eoff) // glog.V(logger.Detail).Infof("inh len(b): %v, off: %v eoff: %v ", len(b), off, eoff)
// glog.V(logger.Detail).Infof("[BZZ] depth: %v, loff: %v, eoff: %v, chunk.Size: %v, treeSize: %v", depth, off, eoff, chunk.Size, treeSize) // glog.V(logger.Detail).Infof("[BZZ] depth: %v, loff: %v, eoff: %v, chunk.Size: %v, treeSize: %v", depth, off, eoff, chunk.Size, treeSize)
@ -376,7 +375,11 @@ func (self *LazyChunkReader) join(b []byte, off int64, eoff int64, depth int, tr
// leaf chunk found // leaf chunk found
if depth == 0 { if depth == 0 {
glog.V(logger.Detail).Infof("[BZZ] depth: %v, len(b): %v, off: %v, eoff: %v, chunk.Size: %v, treeSize: %v", depth, len(b), off, eoff, chunk.Size, treeSize) // glog.V(logger.Detail).Infof("[BZZ] depth: %v, len(b): %v, off: %v, eoff: %v, chunk.Size: %v %v, treeSize: %v", depth, len(b), off, eoff, chunk.Size, len(chunk.SData), treeSize)
extra := 8 + eoff - int64(len(chunk.SData))
if extra > 0 {
eoff -= extra
}
copy(b, chunk.SData[8+off:8+eoff]) copy(b, chunk.SData[8+off:8+eoff])
return // simply give back the chunks reader for content chunks return // simply give back the chunks reader for content chunks
} }
@ -387,7 +390,7 @@ func (self *LazyChunkReader) join(b []byte, off int64, eoff int64, depth int, tr
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
defer wg.Wait() defer wg.Wait()
glog.V(logger.Detail).Infof("[BZZ] start %v,end %v", start, end) // glog.V(logger.Detail).Infof("[BZZ] start %v,end %v", start, end)
for i := start; i < end; i++ { for i := start; i < end; i++ {
soff := i * treeSize soff := i * treeSize
@ -457,7 +460,7 @@ func retrieve(key Key, chunkC chan *Chunk, quitC chan bool) *Chunk {
// Read keeps a cursor so cannot be called simulateously, see ReadAt // Read keeps a cursor so cannot be called simulateously, see ReadAt
func (self *LazyChunkReader) Read(b []byte) (read int, err error) { func (self *LazyChunkReader) Read(b []byte) (read int, err error) {
read, err = self.ReadAt(b, self.off) read, err = self.ReadAt(b, self.off)
glog.V(logger.Detail).Infof("[BZZ] read: %v, off: %v, error: %v", read, self.off, err) // glog.V(logger.Detail).Infof("[BZZ] read: %v, off: %v, error: %v", read, self.off, err)
self.off += int64(read) self.off += int64(read)
return return

View file

@ -2,32 +2,27 @@ package storage
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"runtime" "runtime"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
) )
func init() {
glog.SetV(logger.Info)
glog.SetToStderr(true)
}
/* /*
Tests TreeChunker by splitting and joining a random byte slice Tests TreeChunker by splitting and joining a random byte slice
*/ */
type test interface { type test interface {
Fatalf(string, ...interface{}) Fatalf(string, ...interface{})
Logf(string, ...interface{})
} }
type chunkerTester struct { type chunkerTester struct {
chunks []*Chunk inputs map[uint64][]byte
chunks map[string]*Chunk
t test t test
} }
@ -40,7 +35,12 @@ func (self *chunkerTester) checkChunks(t *testing.T, want int) {
func (self *chunkerTester) Split(chunker Splitter, data io.Reader, size int64, chunkC chan *Chunk, swg *sync.WaitGroup) (key Key) { func (self *chunkerTester) Split(chunker Splitter, data io.Reader, size int64, chunkC chan *Chunk, swg *sync.WaitGroup) (key Key) {
// reset // reset
self.chunks = nil self.chunks = make(map[string]*Chunk)
if self.inputs == nil {
self.inputs = make(map[uint64][]byte)
}
quitC := make(chan bool) quitC := make(chan bool)
timeout := time.After(600 * time.Second) timeout := time.After(600 * time.Second)
if chunkC != nil { if chunkC != nil {
@ -57,7 +57,8 @@ func (self *chunkerTester) Split(chunker Splitter, data io.Reader, size int64, c
return return
} }
// glog.V(logger.Info).Infof("chunk %v received", len(self.chunks)) // glog.V(logger.Info).Infof("chunk %v received", len(self.chunks))
self.chunks = append(self.chunks, chunk) // self.chunks = append(self.chunks, chunk)
self.chunks[chunk.Key.String()] = chunk
if chunk.wg != nil { if chunk.wg != nil {
chunk.wg.Done() chunk.wg.Done()
} }
@ -73,22 +74,26 @@ func (self *chunkerTester) Split(chunker Splitter, data io.Reader, size int64, c
if swg != nil { if swg != nil {
// glog.V(logger.Info).Infof("Waiting for storage to finish") // glog.V(logger.Info).Infof("Waiting for storage to finish")
swg.Wait() swg.Wait()
// glog.V(logger.Info).Infof("St orage finished") // glog.V(logger.Info).Infof("Storage finished")
} }
close(chunkC) close(chunkC)
} }
if chunkC != nil { if chunkC != nil {
// glog.V(logger.Info).Infof("waiting for splitter finished")
<-quitC <-quitC
// glog.V(logger.Info).Infof("Splitter finished")
} }
return return
} }
func (self *chunkerTester) Join(chunker *TreeChunker, key Key, c int, chunkC chan *Chunk, quitC chan bool) LazySectionReader { func (self *chunkerTester) Join(chunker Chunker, key Key, c int, chunkC chan *Chunk, quitC chan bool) LazySectionReader {
// reset but not the chunks // reset but not the chunks
// glog.V(logger.Info).Infof("Splitter finished")
reader := chunker.Join(key, chunkC) reader := chunker.Join(key, chunkC)
timeout := time.After(600 * time.Second) timeout := time.After(600 * time.Second)
// glog.V(logger.Info).Infof("Splitter finished")
i := 0 i := 0
go func() { go func() {
for { for {
@ -101,55 +106,59 @@ func (self *chunkerTester) Join(chunker *TreeChunker, key Key, c int, chunkC cha
close(quitC) close(quitC)
return return
} }
i++ // glog.V(logger.Info).Infof("chunk %v: %v", i, chunk.Key.String())
// this just mocks the behaviour of a chunk store retrieval // this just mocks the behaviour of a chunk store retrieval
var found bool stored, success := self.chunks[chunk.Key.String()]
for _, ch := range self.chunks { // glog.V(logger.Info).Infof("chunk %v, success: %v", chunk.Key.String(), success)
if bytes.Equal(chunk.Key, ch.Key) { if !success {
found = true self.t.Fatalf("not found")
chunk.SData = ch.SData return
break
}
}
if !found {
self.t.Fatalf("not found ")
} }
// glog.V(logger.Info).Infof("chunk %v: %v", i, chunk.Key.String())
chunk.SData = stored.SData
chunk.Size = int64(binary.LittleEndian.Uint64(chunk.SData[0:8]))
close(chunk.C) close(chunk.C)
i++
} }
} }
}() }()
return reader return reader
} }
func testRandomData(n int, chunks int, t *testing.T) { func testRandomData(splitter Splitter, n int, tester *chunkerTester) {
chunker := NewTreeChunker(&ChunkerParams{ if tester.inputs == nil {
Branches: 128, tester.inputs = make(map[uint64][]byte)
Hash: "SHA3", }
}) input, found := tester.inputs[uint64(n)]
tester := &chunkerTester{t: t} var data io.Reader
data, input := testDataReaderAndSlice(n) if !found {
data, input = testDataReaderAndSlice(n)
tester.inputs[uint64(n)] = input
} else {
data = limitReader(bytes.NewReader(input), n)
}
chunkC := make(chan *Chunk, 1000) chunkC := make(chan *Chunk, 1000)
swg := &sync.WaitGroup{} swg := &sync.WaitGroup{}
splitter := chunker
key := tester.Split(splitter, data, int64(n), chunkC, swg) key := tester.Split(splitter, data, int64(n), chunkC, swg)
tester.t.Logf(" Key = %v\n", key)
// t.Logf(" Key = %v\n", key)
// tester.checkChunks(t, chunks)
chunkC = make(chan *Chunk, 1000) chunkC = make(chan *Chunk, 1000)
quitC := make(chan bool) quitC := make(chan bool)
chunker := NewTreeChunker(NewChunkerParams())
reader := tester.Join(chunker, key, 0, chunkC, quitC) reader := tester.Join(chunker, key, 0, chunkC, quitC)
output := make([]byte, n) output := make([]byte, n)
// glog.V(logger.Info).Infof(" Key = %v\n", key)
r, err := reader.Read(output) r, err := reader.Read(output)
// glog.V(logger.Info).Infof(" read = %v %v\n", r, err)
if r != n || err != io.EOF { if r != n || err != io.EOF {
t.Fatalf("read error read: %v n = %v err = %v\n", r, n, err) tester.t.Fatalf("read error read: %v n = %v err = %v\n", r, n, err)
} }
if input != nil { if input != nil {
if !bytes.Equal(output, input) { if !bytes.Equal(output, input) {
t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", input, output) tester.t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", input, output)
} }
} }
close(chunkC) close(chunkC)
@ -157,10 +166,17 @@ func testRandomData(n int, chunks int, t *testing.T) {
} }
func TestRandomData(t *testing.T) { func TestRandomData(t *testing.T) {
testRandomData(60, 1, t) // sizes := []int{123456}
testRandomData(83, 3, t) sizes := []int{1, 60, 83, 179, 253, 1024, 4095, 4096, 4097, 123456}
testRandomData(179, 5, t) tester := &chunkerTester{t: t}
testRandomData(253, 7, t) chunker := NewTreeChunker(NewChunkerParams())
for _, s := range sizes {
testRandomData(chunker, s, tester)
}
pyramid := NewPyramidChunker(NewChunkerParams())
for _, s := range sizes {
testRandomData(pyramid, s, tester)
}
} }
func readAll(reader LazySectionReader, result []byte) { func readAll(reader LazySectionReader, result []byte) {
@ -186,11 +202,9 @@ func benchReadAll(reader LazySectionReader) {
} }
func benchmarkJoin(n int, t *testing.B) { func benchmarkJoin(n int, t *testing.B) {
t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
chunker := NewTreeChunker(&ChunkerParams{ chunker := NewTreeChunker(NewChunkerParams())
Branches: 128,
Hash: "SHA3",
})
tester := &chunkerTester{t: t} tester := &chunkerTester{t: t}
data := testDataReader(n) data := testDataReader(n)
@ -198,24 +212,24 @@ func benchmarkJoin(n int, t *testing.B) {
swg := &sync.WaitGroup{} swg := &sync.WaitGroup{}
key := tester.Split(chunker, data, int64(n), chunkC, swg) key := tester.Split(chunker, data, int64(n), chunkC, swg)
t.StartTimer() // t.StartTimer()
chunkC = make(chan *Chunk, 1000) chunkC = make(chan *Chunk, 1000)
quitC := make(chan bool) quitC := make(chan bool)
reader := tester.Join(chunker, key, i, chunkC, quitC) reader := tester.Join(chunker, key, i, chunkC, quitC)
t.StopTimer()
benchReadAll(reader) benchReadAll(reader)
close(chunkC) close(chunkC)
<-quitC <-quitC
// t.StopTimer()
} }
stats := new(runtime.MemStats)
runtime.ReadMemStats(stats)
fmt.Println(stats.Sys)
} }
func benchmarkSplitTree(n int, t *testing.B) { func benchmarkSplitTree(n int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
chunker := NewTreeChunker(&ChunkerParams{ chunker := NewTreeChunker(NewChunkerParams())
Branches: 128,
Hash: "SHA3",
})
tester := &chunkerTester{t: t} tester := &chunkerTester{t: t}
data := testDataReader(n) data := testDataReader(n)
// glog.V(logger.Info).Infof("splitting data of length %v", n) // glog.V(logger.Info).Infof("splitting data of length %v", n)
@ -229,10 +243,7 @@ func benchmarkSplitTree(n int, t *testing.B) {
func benchmarkSplitPyramid(n int, t *testing.B) { func benchmarkSplitPyramid(n int, t *testing.B) {
t.ReportAllocs() t.ReportAllocs()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
splitter := NewPyramidChunker(&ChunkerParams{ splitter := NewPyramidChunker(NewChunkerParams())
Branches: 128,
Hash: "SHA3",
})
tester := &chunkerTester{t: t} tester := &chunkerTester{t: t}
data := testDataReader(n) data := testDataReader(n)
// glog.V(logger.Info).Infof("splitting data of length %v", n) // glog.V(logger.Info).Infof("splitting data of length %v", n)
@ -243,11 +254,13 @@ func benchmarkSplitPyramid(n int, t *testing.B) {
fmt.Println(stats.Sys) fmt.Println(stats.Sys)
} }
func BenchmarkJoin_100_2(t *testing.B) { benchmarkJoin(100, t) } func BenchmarkJoin_2(t *testing.B) { benchmarkJoin(100, t) }
func BenchmarkJoin_1000_2(t *testing.B) { benchmarkJoin(1000, t) } func BenchmarkJoin_3(t *testing.B) { benchmarkJoin(1000, t) }
func BenchmarkJoin_10000_2(t *testing.B) { benchmarkJoin(10000, t) } func BenchmarkJoin_4(t *testing.B) { benchmarkJoin(10000, t) }
func BenchmarkJoin_100000_2(t *testing.B) { benchmarkJoin(100000, t) } func BenchmarkJoin_5(t *testing.B) { benchmarkJoin(100000, t) }
func BenchmarkJoin_1000000_2(t *testing.B) { benchmarkJoin(1000000, t) } func BenchmarkJoin_6(t *testing.B) { benchmarkJoin(1000000, t) }
func BenchmarkJoin_7(t *testing.B) { benchmarkJoin(10000000, t) }
func BenchmarkJoin_8(t *testing.B) { benchmarkJoin(100000000, t) }
func BenchmarkSplitTree_2(t *testing.B) { benchmarkSplitTree(100, t) } func BenchmarkSplitTree_2(t *testing.B) { benchmarkSplitTree(100, t) }
func BenchmarkSplitTree_2h(t *testing.B) { benchmarkSplitTree(500, t) } func BenchmarkSplitTree_2h(t *testing.B) { benchmarkSplitTree(500, t) }

View file

@ -11,8 +11,32 @@ import (
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
) )
type limitedReader struct {
r io.Reader
off int64
size int64
}
func limitReader(r io.Reader, size int) *limitedReader {
return &limitedReader{r, 0, int64(size)}
}
func (self *limitedReader) Read(buf []byte) (int, error) {
limit := int64(len(buf))
left := self.size - self.off
if limit >= left {
limit = left
}
n, err := self.r.Read(buf[:limit])
if err == nil && limit == left {
err = io.EOF
}
self.off += int64(n)
return n, err
}
func testDataReader(l int) (r io.Reader) { func testDataReader(l int) (r io.Reader) {
return io.LimitReader(rand.Reader, int64(l)) return limitReader(rand.Reader, l)
} }
func testDataReaderAndSlice(l int) (r io.Reader, slice []byte) { func testDataReaderAndSlice(l int) (r io.Reader, slice []byte) {
@ -20,7 +44,7 @@ func testDataReaderAndSlice(l int) (r io.Reader, slice []byte) {
if _, err := rand.Read(slice); err != nil { if _, err := rand.Read(slice); err != nil {
panic("rand error") panic("rand error")
} }
r = bytes.NewReader(slice) r = limitReader(bytes.NewReader(slice), l)
return return
} }

View file

@ -23,12 +23,12 @@ implementation for storage or retrieval.
*/ */
const ( const (
storeChanCapacity = 100 storeChanCapacity = 1000
retrieveChanCapacity = 100 retrieveChanCapacity = 1000
singletonSwarmDbCapacity = 50000 singletonSwarmDbCapacity = 50000
singletonSwarmCacheCapacity = 500 singletonSwarmCacheCapacity = 500
maxStoreProcesses = 100 maxStoreProcesses = 100
maxRetrieveProcesses = 100 maxRetrieveProcesses = 100
) )
var ( var (
@ -44,7 +44,7 @@ type DPA struct {
lock sync.Mutex lock sync.Mutex
running bool running bool
wg *sync.WaitGroup wg *sync.WaitGroup
quitC chan bool quitC chan bool
} }
// for testing locally // for testing locally
@ -112,58 +112,58 @@ func (self *DPA) Stop() {
// retrieveLoop dispatches the parallel chunk retrieval requests received on the // retrieveLoop dispatches the parallel chunk retrieval requests received on the
// retrieve channel to its ChunkStore (NetStore or LocalStore) // retrieve channel to its ChunkStore (NetStore or LocalStore)
func (self *DPA) retrieveLoop() { func (self *DPA) retrieveLoop() {
for i:=0; i< maxRetrieveProcesses; i++ { for i := 0; i < maxRetrieveProcesses; i++ {
go self.retrieveWorker() go self.retrieveWorker()
} }
glog.V(logger.Detail).Infof("[BZZ] dpa: retrieve loop spawning %v workers", maxRetrieveProcesses) glog.V(logger.Detail).Infof("[BZZ] dpa: retrieve loop spawning %v workers", maxRetrieveProcesses)
} }
func (self *DPA) retrieveWorker() { func (self *DPA) retrieveWorker() {
for chunk := range self.retrieveC { for chunk := range self.retrieveC {
glog.V(logger.Detail).Infof("[BZZ] dpa: retrieve loop : chunk %v", chunk.Key.Log()) glog.V(logger.Detail).Infof("[BZZ] dpa: retrieve loop : chunk %v", chunk.Key.Log())
storedChunk, err := self.Get(chunk.Key) storedChunk, err := self.Get(chunk.Key)
if err == notFound { if err == notFound {
glog.V(logger.Detail).Infof("[BZZ] chunk %v not found", chunk.Key.Log()) glog.V(logger.Detail).Infof("[BZZ] chunk %v not found", chunk.Key.Log())
} else if err != nil { } else if err != nil {
glog.V(logger.Detail).Infof("[BZZ] error retrieving chunk %v: %v", chunk.Key.Log(), err) glog.V(logger.Detail).Infof("[BZZ] error retrieving chunk %v: %v", chunk.Key.Log(), err)
} else { } else {
chunk.SData = storedChunk.SData chunk.SData = storedChunk.SData
chunk.Size = storedChunk.Size chunk.Size = storedChunk.Size
} }
close(chunk.C) close(chunk.C)
select { select {
case <-self.quitC: case <-self.quitC:
return return
default: default:
}
} }
} }
}
// storeLoop dispatches the parallel chunk store request processors // storeLoop dispatches the parallel chunk store request processors
// received on the store channel to its ChunkStore (NetStore or LocalStore) // received on the store channel to its ChunkStore (NetStore or LocalStore)
func (self *DPA) storeLoop() { func (self *DPA) storeLoop() {
for i:=0; i< maxStoreProcesses; i++ { for i := 0; i < maxStoreProcesses; i++ {
go self.storeWorker() go self.storeWorker()
} }
glog.V(logger.Detail).Infof("[BZZ] dpa: store spawning %v workers", maxStoreProcesses) glog.V(logger.Detail).Infof("[BZZ] dpa: store spawning %v workers", maxStoreProcesses)
} }
func (self *DPA) storeWorker() { func (self *DPA) storeWorker() {
for chunk := range self.storeC { for chunk := range self.storeC {
self.Put(chunk) self.Put(chunk)
if chunk.wg != nil { if chunk.wg != nil {
glog.V(logger.Detail).Infof("[BZZ] dpa: store processor %v", chunk.Key.Log()) glog.V(logger.Detail).Infof("[BZZ] dpa: store processor %v", chunk.Key.Log())
chunk.wg.Done() chunk.wg.Done()
} }
select { select {
case <-self.quitC: case <-self.quitC:
return return
default: default:
} }
} }
} }
// DpaChunkStore implements the ChunkStore interface, // DpaChunkStore implements the ChunkStore interface,

View file

@ -1,13 +1,14 @@
package storage package storage
import ( import (
"encoding/binary"
"fmt"
"io" "io"
"math" "math"
"strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
) )
const ( const (
@ -22,12 +23,22 @@ type Tree struct {
type Node struct { type Node struct {
Pending int64 Pending int64
Size uint64
Children []common.Hash Children []common.Hash
Last bool Last bool
} }
func (self *Node) String() string {
var children []string
for _, node := range self.Children {
children = append(children, node.Hex())
}
return fmt.Sprintf("pending: %v, size: %v, last :%v, children: %v", self.Pending, self.Size, self.Last, strings.Join(children, ", "))
}
type Task struct { type Task struct {
Index int64 // Index of the chunk being processed Index int64 // Index of the chunk being processed
Size uint64
Data []byte // Binary blob of the chunk Data []byte // Binary blob of the chunk
Last bool Last bool
} }
@ -54,7 +65,7 @@ func (self *PyramidChunker) Split(data io.Reader, size int64, chunkC chan *Chunk
chunks := (size + self.chunkSize - 1) / self.chunkSize chunks := (size + self.chunkSize - 1) / self.chunkSize
depth := int(math.Ceil(math.Log(float64(chunks))/math.Log(float64(self.branches)))) + 1 depth := int(math.Ceil(math.Log(float64(chunks))/math.Log(float64(self.branches)))) + 1
glog.V(logger.Detail).Infof("chunks: %v, depth: %v", chunks, depth) // glog.V(logger.Detail).Infof("chunks: %v, depth: %v", chunks, depth)
results := Tree{ results := Tree{
Chunks: chunks, Chunks: chunks,
@ -69,22 +80,24 @@ func (self *PyramidChunker) Split(data io.Reader, size int64, chunkC chan *Chunk
abortC := make(chan bool) abortC := make(chan bool)
for i := 0; i < processors; i++ { for i := 0; i < processors; i++ {
pend.Add(1) pend.Add(1)
go self.processor(pend, tasks, &results) go self.processor(pend, swg, tasks, chunkC, &results)
} }
// Feed the chunks into the task pool // Feed the chunks into the task pool
for index := 0; ; index++ { for index := 0; ; index++ {
buffer := make([]byte, self.chunkSize+8) buffer := make([]byte, self.chunkSize+8)
n, err := io.ReadFull(data, buffer) n, err := data.Read(buffer[8:])
last := err == io.ErrUnexpectedEOF last := err == io.ErrUnexpectedEOF || err == io.EOF
// glog.V(logger.Detail).Infof("n: %v, index: %v, depth: %v", n, index, depth)
if err != nil && !last { if err != nil && !last {
glog.V(logger.Info).Infof("error: %v", err) // glog.V(logger.Info).Infof("error: %v", err)
close(abortC) close(abortC)
break
} }
binary.LittleEndian.PutUint64(buffer[:8], uint64(n))
pend.Add(1) pend.Add(1)
// glog.V(logger.Info).Infof("-> task %v (%v)", index, n) // glog.V(logger.Info).Infof("-> task %v (%v)", index, n)
select { select {
case tasks <- &Task{Index: int64(index), Data: buffer[:n+8], Last: last}: case tasks <- &Task{Index: int64(index), Size: uint64(n), Data: buffer[:n+8], Last: last}:
case <-abortC: case <-abortC:
return nil, err return nil, err
} }
@ -102,7 +115,7 @@ func (self *PyramidChunker) Split(data io.Reader, size int64, chunkC chan *Chunk
return key, nil return key, nil
} }
func (self *PyramidChunker) processor(pend *sync.WaitGroup, tasks chan *Task, results *Tree) { func (self *PyramidChunker) processor(pend, swg *sync.WaitGroup, tasks chan *Task, chunkC chan *Chunk, results *Tree) {
defer pend.Done() defer pend.Done()
// glog.V(logger.Info).Infof("processor started") // glog.V(logger.Info).Infof("processor started")
@ -111,16 +124,22 @@ func (self *PyramidChunker) processor(pend *sync.WaitGroup, tasks chan *Task, re
for task := range tasks { for task := range tasks {
depth, pow := len(results.Levels)-1, self.branches depth, pow := len(results.Levels)-1, self.branches
// glog.V(logger.Info).Infof("task: %v, last: %v", task.Index, task.Last) // glog.V(logger.Info).Infof("task: %v, last: %v", task.Index, task.Last)
size := task.Size
data := task.Data
var node *Node var node *Node
for depth >= 0 { for depth >= 0 {
// New chunk received, reset the hasher and start processing // New chunk received, reset the hasher and start processing
hasher.Reset() hasher.Reset()
if node == nil { // Leaf node, hash the data chunk if node == nil { // Leaf node, hash the data chunk
hasher.Write(task.Data) hasher.Write(task.Data)
} else { // Internal node, hash the children } else { // Internal node, hash the children
for _, hash := range node.Children { size = node.Size
data = make([]byte, hasher.Size()*len(node.Children)+8)
binary.LittleEndian.PutUint64(data[:8], size)
hasher.Write(data[:8])
for i, hash := range node.Children {
copy(data[i*hasher.Size()+8:], hash[:])
hasher.Write(hash[:]) hasher.Write(hash[:])
} }
} }
@ -134,22 +153,33 @@ func (self *PyramidChunker) processor(pend *sync.WaitGroup, tasks chan *Task, re
if task.Index/pow == results.Chunks/pow { if task.Index/pow == results.Chunks/pow {
pending = (results.Chunks + pow/self.branches - 1) / (pow / self.branches) % self.branches pending = (results.Chunks + pow/self.branches - 1) / (pow / self.branches) % self.branches
} }
node = &Node{pending, make([]common.Hash, pending), last} node = &Node{pending, 0, make([]common.Hash, pending), last}
results.Levels[depth][task.Index/pow] = node results.Levels[depth][task.Index/pow] = node
// glog.V(logger.Info).Infof("create node %v, %v (%v children, all pending)", depth, task.Index/pow, pending)
} }
node.Pending-- node.Pending--
// glog.V(logger.Info).Infof("pending now: %v", node.Pending)
i := task.Index / (pow / self.branches) % self.branches i := task.Index / (pow / self.branches) % self.branches
if last { if last {
node.Pending -= self.branches - i
node.Children = node.Children[:i+1]
node.Last = true node.Last = true
} }
copy(node.Children[i][:], hash) copy(node.Children[i][:], hash)
node.Size += size
left := node.Pending left := node.Pending
// glog.V(logger.Info).Infof("left pending now: %v, node size: %v", left, node.Size)
if chunkC != nil {
if swg != nil {
swg.Add(1)
}
select {
case chunkC <- &Chunk{Key: hash, SData: data, wg: swg}:
// case <- self.quitC
}
}
if depth+1 < len(results.Levels) { if depth+1 < len(results.Levels) {
delete(results.Levels[depth+1], task.Index/(pow/self.branches)) delete(results.Levels[depth+1], task.Index/(pow/self.branches))
} }
results.Lock.Unlock() results.Lock.Unlock()
// If there's more work to be done, leave for others // If there's more work to be done, leave for others
// glog.V(logger.Info).Infof("left %v", left) // glog.V(logger.Info).Infof("left %v", left)