Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open telemetry and new op benchmarking #5

Merged
merged 19 commits into from
Feb 13, 2024
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
101 changes: 101 additions & 0 deletions benchmarking/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"encoding/binary"
"flag"
"fmt"
"os"
"sync"

"github.com/gnolang/gno/benchmarking"
"github.com/gnolang/gno/gnovm/pkg/gnolang"
)

const recordSize int = 10

var pathFlag = flag.String("path", "", "the path to the benchmark file")

func main() {
flag.Parse()

file, err := os.Open(*pathFlag)
if err != nil {
panic("could not create benchmark file: " + err.Error())
}

inputCh := make(chan []byte, 10000)
outputCh := make(chan string, 10000)
wg := sync.WaitGroup{}
numWorkers := 4
wg.Add(numWorkers)

doneCh := make(chan struct{})

for i := 0; i < numWorkers; i++ {
go func() {
for {
buf, ok := <-inputCh
if !ok {
break
}

opName := gnolang.Op(buf[0]).String()
if buf[1] != 0 {
opName = benchmarking.OpCodeString(buf[1])
}

elapsedTime := binary.LittleEndian.Uint32(buf[2:])
size := binary.LittleEndian.Uint32(buf[6:])
outputCh <- opName + "," + fmt.Sprint(elapsedTime) + "," + fmt.Sprint(size)
}
wg.Done()
}()
}

go func() {
out, err := os.Create("results.csv")
if err != nil {
panic("could not create readable output file: " + err.Error())
}

fmt.Fprintln(out, "op,elapsedTime,diskIOBytes")

for {
output, ok := <-outputCh
if !ok {
break
}

fmt.Fprintln(out, output)
}

out.Close()
doneCh <- struct{}{}
}()

var i int
bufSize := recordSize * 100000
for {
buf := make([]byte, bufSize)
if n, err := file.Read(buf); err != nil && n == 0 {
break
}

for j := 0; j < len(buf)/recordSize; j += recordSize {
inputCh <- buf[j : j+recordSize]
}

i += bufSize / recordSize
if i%1000 == 0 {
fmt.Println(i)
}
}

close(inputCh)
wg.Wait()
close(outputCh)
<-doneCh
close(doneCh)

fmt.Println("done")
}
13 changes: 13 additions & 0 deletions benchmarking/enabled.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package benchmarking

var enabled bool

func Enabled() bool {
return enabled
}

func Init(filepath string) {
enabled = true
initExporter(filepath)
initStack()
}
64 changes: 64 additions & 0 deletions benchmarking/exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package benchmarking

import (
"encoding/binary"
"os"
"time"
)

const flushTimerInterval = time.Duration(time.Second * 30)

Check failure on line 9 in benchmarking/exporter.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary conversion (unconvert)

var fileWriter *exporter

func initExporter(fileName string) {
file, err := os.Create(fileName)
if err != nil {
panic("could not create benchmark file: " + err.Error())
}

fileWriter = &exporter{
file: file,
bytesToFlushAfter: 10 * 100000,
flushTimer: *time.NewTimer(flushTimerInterval),
}

go func() {
for {
<-fileWriter.flushTimer.C
fileWriter.file.Sync()
fileWriter.flushTimer.Reset(flushTimerInterval)
}
}()
}

type exporter struct {
file *os.File
bytesWritten int
bytesToFlushAfter int
flushTimer time.Timer
}

func (e *exporter) export(opCode OpCode, elapsedTime time.Duration, size uint32) {
buf := []byte{opCode[0], opCode[1], 0, 0, 0, 0, 0, 0, 0, 0}
binary.LittleEndian.PutUint32(buf[2:], uint32(elapsedTime))
binary.LittleEndian.PutUint32(buf[6:], size)
n, err := e.file.Write(buf)
if err != nil {
panic("could not write to benchmark file: " + err.Error())
}

e.bytesWritten += n
if e.bytesWritten > e.bytesToFlushAfter {
e.file.Sync()
e.bytesWritten = 0
e.flushTimer.Reset(flushTimerInterval)
}
}

func (e *exporter) close() {
e.file.Close()
}

func Finish() {
fileWriter.close()
}
37 changes: 37 additions & 0 deletions benchmarking/measurement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package benchmarking

import (
"time"
)

type measurement struct {
*timer
opCode OpCode
allocation uint32
}

func startNewMeasurement(opCode OpCode) *measurement {
return &measurement{
timer: &timer{startTime: time.Now()},
opCode: opCode,
}
}

func (m *measurement) pause() {
m.stop()
}

func (m *measurement) resume() {
m.start()
}

func (m *measurement) end(size uint32) {
m.stop()
if size != 0 && m.allocation != 0 {
panic("measurement cannot have both allocation and size")
} else if size == 0 {
size = m.allocation
}

fileWriter.export(m.opCode, m.elapsedTime, size)
}
49 changes: 49 additions & 0 deletions benchmarking/ops.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package benchmarking

const (
OpStoreGetObject byte = 0x01 // get value from store
OpStoreSetObject byte = 0x02 // set value in store
OpStoreDeleteObject byte = 0x03 // delete value from store
OpStoreGetPackage byte = 0x04 // get package from store
OpStoreGetType byte = 0x05 // get type from store
OpStoreSetType byte = 0x06 // set type in store
OpStoreGetBlockNode byte = 0x07 // get block node from store
OpStoreSetBlockNode byte = 0x08 // set block node in store
OpStoreAddMemPackage byte = 0x09 // add mempackage to store
OpStoreGetMemPackage byte = 0x0A // get mempackage from store
OpFinalizeTx byte = 0x0B // finalize realm transaction

invalidStorageOp string = "OpStoreInvalid"
)

var opCodeNames = []string{
invalidStorageOp,
"OpStoreGetObject",
"OpStoreSetObject",
"OpStoreDeleteObject",
"OpStoreGetPackage",
"OpStoreGetType",
"OpStoreSetType",
"OpStoreGetBlockNode",
"OpStoreSetBlockNode",
"OpStoreAddMemPackage",
"OpStoreGetMemPackage",
"OpFinalizeTx",
}

type OpCode [2]byte

func VMOpCode(op byte) OpCode {
return [2]byte{op, 0x00}
}

func StorageOpCode(op byte) OpCode {
return [2]byte{0x00, op}
}

func OpCodeString(op byte) string {
if int(op) >= len(opCodeNames) {
return invalidStorageOp
}
return opCodeNames[op]
}
51 changes: 51 additions & 0 deletions benchmarking/stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package benchmarking

const initStackSize int = 64

var (
measurementStack []*measurement
stackSize int
)

func initStack() {
measurementStack = make([]*measurement, initStackSize)
}

func StartMeasurement(opCode OpCode) {
if stackSize != 0 {
measurementStack[stackSize-1].pause()
}

if stackSize == len(measurementStack) {
newStack := make([]*measurement, stackSize*2)
copy(newStack, measurementStack)
measurementStack = newStack
}

measurementStack[stackSize] = startNewMeasurement(opCode)
stackSize++
}

// StopMeasurement ends the current measurement and resumes the previous one
// if one exists. It accepts the number of bytes that were read/written to/from
// the store. This value is zero if the operation is not a read or write.
func StopMeasurement(size uint32) {
if stackSize == 0 {
return
}

stackSize--
measurementStack[stackSize].end(size)

if stackSize != 0 {
measurementStack[stackSize].resume()
}
}

func RecordAllocation(size uint32) {
if stackSize == 0 {
return
}

measurementStack[stackSize-1].allocation += size
}
22 changes: 22 additions & 0 deletions benchmarking/timer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package benchmarking

import "time"

type timer struct {
startTime time.Time
elapsedTime time.Duration
isStopped bool
}

func (t *timer) start() {
t.startTime = time.Now()
}

func (t *timer) stop() {
if t.isStopped {
return
}

t.elapsedTime += time.Since(t.startTime)
t.isStopped = true
}
Loading
Loading