Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/sha256"
"encoding/binary"
"fmt"
"time"
)

// Block contains a propsed "command", metadata for the protocol, and a link to the "parent" block.
Expand All @@ -15,6 +16,7 @@ type Block struct {
cmd Command
cert QuorumCert
view View
ts time.Time
}

// NewBlock creates a new Block
Expand All @@ -25,6 +27,7 @@ func NewBlock(parent Hash, cert QuorumCert, cmd Command, view View, proposer ID)
cmd: cmd,
view: view,
proposer: proposer,
ts: time.Now(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I understand is that Block is sent over the wire. So, if a remote replica created this block, won't the latency be incorrect since the nodes don't have perfectly synchronized time?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Block is sent on the wire like this:

message Block {
  bytes Parent = 1;
  QuorumCert QC = 2;
  uint64 View = 3;
  bytes Command = 4;
  uint32 Proposer = 5;
}

So this ts field is not sent over the wire; I believe it is a local-only timestamp.

@hanish520 Please confirm my understanding. I also suggest to rename the field to timestamp

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So the timestamp is recorded here:

ts: time.Now(),

but NewBlock is used to convert from proto to the Block:

return hotstuff.NewBlock(

As I understand, it measures "local commit latency". I.e., it measures the latency between block arrival and commit if the replica is an acceptor. For leader, this latency measurement is very tiny, no? @hanish520 is this the intention?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This metrics is used to measure the commit latency at the leader when used in the network simulation mode. This metrics may not make sense in the normal mode.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with network simulation mode? Is this done with hotstuff run locally?

Otherwise I understand, and I would suggest a different way to do this: put timestamps in propose and commit events instead of the block. Then in the ConsensusLatency, you can filter blocks that were committed/proposed by other replicas than yourself. This way it could work regardless of the mode.

I can come up with some code for this soon

}
// cache the hash immediately because it is too racy to do it in Hash()
b.hash = sha256.Sum256(b.ToBytes())
Expand Down Expand Up @@ -72,6 +75,11 @@ func (b *Block) View() View {
return b.view
}

// TimeStamp returns the timestamp of the block
func (b *Block) TimeStamp() time.Time {
return b.ts
}

// ToBytes returns the raw byte form of the Block, to be used for hashing, etc.
func (b *Block) ToBytes() []byte {
buf := b.parent[:]
Expand Down
2 changes: 2 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package consensus
import (
"fmt"
"sync"
"time"

"github.com/relab/hotstuff"
"github.com/relab/hotstuff/eventloop"
Expand Down Expand Up @@ -282,6 +283,7 @@ func (cs *consensusBase) commitInner(block *hotstuff.Block) error {
} else {
return fmt.Errorf("failed to locate block: %s", block.Parent())
}
cs.eventLoop.AddEvent(hotstuff.ConsensusLatencyEvent{Latency: time.Since(block.TimeStamp())})
cs.logger.Debug("EXEC: ", block)
cs.executor.Exec(block)
cs.bExec = block
Expand Down
70 changes: 70 additions & 0 deletions metrics/consensuslatency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package metrics

import (
"time"

"github.com/relab/hotstuff"
"github.com/relab/hotstuff/eventloop"
"github.com/relab/hotstuff/logging"
"github.com/relab/hotstuff/metrics/types"
"github.com/relab/hotstuff/modules"
)

func init() {
RegisterReplicaMetric("consensus-latency", func() any {
return &ConsensusLatency{}
})
}

// ConsensusLatency processes consensus latency measurements and writes them to the metrics logger.
type ConsensusLatency struct {
metricsLogger Logger
id hotstuff.ID
wf Welford
}

// InitModule gives the module access to the other modules.
func (lr *ConsensusLatency) InitModule(mods *modules.Core) {
var (
eventLoop *eventloop.EventLoop
logger logging.Logger
opts *modules.Options
)

mods.Get(
&lr.metricsLogger,
opts,
&eventLoop,
&logger,
)

lr.id = opts.ID()
eventLoop.RegisterHandler(hotstuff.ConsensusLatencyEvent{}, func(event any) {
latencyEvent := event.(hotstuff.ConsensusLatencyEvent)
lr.addLatency(latencyEvent.Latency)
})

eventLoop.RegisterHandler(types.TickEvent{}, func(event any) {
lr.tick(event.(types.TickEvent))
}, eventloop.Prioritize())

logger.Info("Consensus Latency metric enabled")
}

// AddLatency adds a latency data point to the current measurement.
func (lr *ConsensusLatency) addLatency(latency time.Duration) {
millis := float64(latency) / float64(time.Millisecond)
lr.wf.Update(millis)
}

func (lr *ConsensusLatency) tick(_ types.TickEvent) {
mean, variance, count := lr.wf.Get()
event := &types.LatencyMeasurement{
Event: types.NewReplicaEvent(uint32(lr.id), time.Now()),
Latency: mean,
Variance: variance,
Count: count,
}
lr.metricsLogger.Log(event)
lr.wf.Reset()
}
5 changes: 5 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"strconv"
"strings"
"time"
)

// IDSet implements a set of replica IDs. It is used to show which replicas participated in some event.
Expand Down Expand Up @@ -383,3 +384,7 @@ func writeParticipants(wr io.Writer, participants IDSet) (err error) {
})
return err
}

type ConsensusLatencyEvent struct {
Latency time.Duration
}