add performance reporting
costs ~9% bomb performance; can be toggled in Rán REPL via 'metrics'
This commit is contained in:
parent
2ec417da6a
commit
8a22c7bf29
|
@ -1,13 +1,76 @@
|
||||||
package pixelflut
|
package pixelflut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @speed: add some performance reporting mechanism on these functions when
|
// Performance contains pixelflut metrics
|
||||||
// called as goroutines
|
type Performance struct {
|
||||||
|
Enabled bool
|
||||||
|
Conns int
|
||||||
|
BytesPerSec int
|
||||||
|
BytesTotal int
|
||||||
|
|
||||||
|
connsReporter chan int
|
||||||
|
bytesReporter chan int
|
||||||
|
bytes int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Performance) String() string {
|
||||||
|
return fmt.Sprintf("%v conns\t%v\t%v/s",
|
||||||
|
p.Conns, fmtBytes(p.BytesTotal), fmtBytes(p.BytesPerSec))
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://yourbasic.org/golang/byte-count.go
|
||||||
|
func fmtBytes(b int) string {
|
||||||
|
const unit = 1024
|
||||||
|
if b < unit {
|
||||||
|
return fmt.Sprintf("%d B", b)
|
||||||
|
}
|
||||||
|
div, exp := int64(unit), 0
|
||||||
|
for n := b / unit; n >= unit; n /= unit {
|
||||||
|
div *= unit
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.1f %ciB",
|
||||||
|
float64(b)/float64(div), "KMGTPE"[exp])
|
||||||
|
}
|
||||||
|
|
||||||
|
// PerformanceReporter provides pixelflut performance metrics, when Enabled is true.
|
||||||
|
// @speed: Note that enabling costs ~9% bomb performance under high throughput.
|
||||||
|
var PerformanceReporter = initPerfReporter()
|
||||||
|
|
||||||
|
// should be called only once
|
||||||
|
func initPerfReporter() *Performance {
|
||||||
|
r := new(Performance)
|
||||||
|
r.bytesReporter = make(chan int, 512)
|
||||||
|
r.connsReporter = make(chan int, 512)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case b := <-r.bytesReporter:
|
||||||
|
r.bytes += b
|
||||||
|
r.BytesTotal += b
|
||||||
|
case c := <-r.connsReporter:
|
||||||
|
r.Conns += c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
r.BytesPerSec = r.bytes
|
||||||
|
r.bytes = 0
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// bombAddress writes the given message via plain TCP to the given address,
|
// bombAddress writes the given message via plain TCP to the given address,
|
||||||
// as fast as possible, until stop is closed.
|
// as fast as possible, until stop is closed.
|
||||||
|
@ -22,15 +85,21 @@ func bombAddress(message []byte, address string, stop chan bool, wg *sync.WaitGr
|
||||||
}
|
}
|
||||||
|
|
||||||
func bombConn(message []byte, conn net.Conn, stop chan bool) {
|
func bombConn(message []byte, conn net.Conn, stop chan bool) {
|
||||||
|
PerformanceReporter.connsReporter <- 1
|
||||||
|
defer func() { PerformanceReporter.connsReporter <- -1 }()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-stop:
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
_, err := conn.Write(message)
|
b, err := conn.Write(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if PerformanceReporter.Enabled {
|
||||||
|
PerformanceReporter.bytesReporter <- b
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,12 @@ type FlutTask struct {
|
||||||
|
|
||||||
type FlutAck struct{ Ok bool }
|
type FlutAck struct{ Ok bool }
|
||||||
|
|
||||||
|
type FlutStatus struct {
|
||||||
|
*pixelflut.Performance
|
||||||
|
Ok bool
|
||||||
|
Fluting bool
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Hevring) Flut(task FlutTask, reply *FlutAck) error {
|
func (h *Hevring) Flut(task FlutTask, reply *FlutAck) error {
|
||||||
if (h.task != FlutTask{}) {
|
if (h.task != FlutTask{}) {
|
||||||
// @incomplete: stop old task if new task is received
|
// @incomplete: stop old task if new task is received
|
||||||
|
@ -56,9 +62,11 @@ func (h *Hevring) Flut(task FlutTask, reply *FlutAck) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hevring) Status(x int, reply *FlutAck) error {
|
func (h *Hevring) Status(metrics bool, reply *FlutStatus) error {
|
||||||
// @incomplete: provide performance metrics
|
pixelflut.PerformanceReporter.Enabled = metrics
|
||||||
|
reply.Performance = pixelflut.PerformanceReporter
|
||||||
reply.Ok = true
|
reply.Ok = true
|
||||||
|
reply.Fluting = h.taskQuit != nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
rpc/ran.go
42
rpc/ran.go
|
@ -1,7 +1,6 @@
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "io"
|
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
@ -12,11 +11,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SpeckiJ/Hochwasser/pixelflut"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Rán struct {
|
type Rán struct {
|
||||||
clients []*rpc.Client
|
clients []*rpc.Client
|
||||||
task FlutTask
|
task FlutTask
|
||||||
|
metrics pixelflut.Performance
|
||||||
}
|
}
|
||||||
|
|
||||||
// SummonRán sets up the RPC master, accepting connections at addres (":1234")
|
// SummonRán sets up the RPC master, accepting connections at addres (":1234")
|
||||||
|
@ -59,11 +61,19 @@ func SummonRán(address string, stopChan chan bool, wg *sync.WaitGroup) *Rán {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
var clients []*rpc.Client
|
var clients []*rpc.Client
|
||||||
|
|
||||||
|
r.metrics.Conns = 0
|
||||||
|
r.metrics.BytesPerSec = 0
|
||||||
|
r.metrics.BytesTotal = 0
|
||||||
|
|
||||||
for _, c := range r.clients {
|
for _, c := range r.clients {
|
||||||
status := FlutAck{}
|
status := FlutStatus{}
|
||||||
err := c.Call("Hevring.Status", 0, &status)
|
err := c.Call("Hevring.Status", r.metrics.Enabled, &status)
|
||||||
if err == nil && status.Ok {
|
if err == nil && status.Ok {
|
||||||
clients = append(clients, c)
|
clients = append(clients, c)
|
||||||
|
r.metrics.Conns += status.Conns
|
||||||
|
r.metrics.BytesPerSec += status.BytesPerSec
|
||||||
|
r.metrics.BytesTotal += status.BytesTotal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(r.clients) != len(clients) {
|
if len(r.clients) != len(clients) {
|
||||||
|
@ -73,6 +83,16 @@ func SummonRán(address string, stopChan chan bool, wg *sync.WaitGroup) *Rán {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// print performance
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
if r.metrics.Enabled {
|
||||||
|
fmt.Println(r.metrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// REPL to change tasks without loosing clients
|
// REPL to change tasks without loosing clients
|
||||||
go func() {
|
go func() {
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
@ -86,6 +106,18 @@ func SummonRán(address string, stopChan chan bool, wg *sync.WaitGroup) *Rán {
|
||||||
c.Call("Hevring.Stop", 0, &ack) // @speed: async
|
c.Call("Hevring.Stop", 0, &ack) // @speed: async
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if cmd == "start" {
|
||||||
|
if (r.task != FlutTask{}) {
|
||||||
|
for _, c := range r.clients {
|
||||||
|
ack := FlutAck{}
|
||||||
|
// @speed: should send tasks async
|
||||||
|
err := c.Call("Hevring.Flut", r.task, &ack)
|
||||||
|
if err != nil || !ack.Ok {
|
||||||
|
log.Printf("[rán] client didn't accept task")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if cmd == "img" && len(args) > 0 {
|
} else if cmd == "img" && len(args) > 0 {
|
||||||
// // @incomplete
|
// // @incomplete
|
||||||
// path := args[0]
|
// path := args[0]
|
||||||
|
@ -97,6 +129,10 @@ func SummonRán(address string, stopChan chan bool, wg *sync.WaitGroup) *Rán {
|
||||||
// offset = image.Pt(x, y)
|
// offset = image.Pt(x, y)
|
||||||
// }
|
// }
|
||||||
// task := FlutTask{}
|
// task := FlutTask{}
|
||||||
|
|
||||||
|
} else if cmd == "metrics" {
|
||||||
|
r.metrics.Enabled = !r.metrics.Enabled
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
Loading…
Reference in New Issue