Skip to content

Commit

Permalink
Bench (#683)
Browse files Browse the repository at this point in the history
* Extended public API

* Refactored event loop

* Refactored parser

* Fixed unit tests

* Added --dry-run opt

* Added total alloc

* Added profiler

* Fixed driver registration
  • Loading branch information
ziflex authored Oct 16, 2021
1 parent 77569f3 commit bd6463f
Show file tree
Hide file tree
Showing 37 changed files with 5,951 additions and 5,410 deletions.
81 changes: 81 additions & 0 deletions benchmarks/drivers/open_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package drivers_test

import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp"
"testing"

"github.com/MontFerret/ferret"
)

var c *ferret.Instance

func init() {
c = ferret.New()
c.Drivers().Register(cdp.NewDriver(), drivers.AsDefault())
}

func Benchmark_Open_CDP(b *testing.B) {
ctx := context.Background()

p, err := c.Compile(`
LET doc = DOCUMENT("https://www.montferret.dev")
RETURN TRUE
`)

if err != nil {
b.Fatal(err)
}

for n := 0; n < b.N; n++ {
if _, err := c.Run(ctx, p); err != nil {
b.Fatal(err)
}
}
}

func Benchmark_Navigate_CDP(b *testing.B) {
ctx := context.Background()

p, err := c.Compile(`
LET doc = DOCUMENT('https://www.theverge.com/tech', {
driver: "cdp",
ignore: {
resources: [
{
url: "*",
type: "image"
}
]
}
})
WAIT_ELEMENT(doc, '.c-compact-river__entry', 5000)
LET articles = ELEMENTS(doc, '.c-entry-box--compact__image-wrapper')
LET links = (
FOR article IN articles
FILTER article.attributes?.href LIKE 'https://www.theverge.com/*'
RETURN article.attributes.href
)
FOR link IN links
LIMIT 10
// The Verge has pretty heavy pages, so let's increase the navigation wait time
NAVIGATE(doc, link, 20000)
WAIT_ELEMENT(doc, '.c-entry-content', 15000)
LET texter = ELEMENT(doc, '.c-entry-content')
RETURN texter.innerText
`)

if err != nil {
b.Fatal(err)
}

for n := 0; n < b.N; n++ {
if _, err := c.Run(ctx, p); err != nil {
b.Fatal(err)
}
}
}
193 changes: 190 additions & 3 deletions e2e/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ package main

import (
"bufio"
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"github.com/rs/zerolog"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
rt "runtime"
"runtime/pprof"
"strings"
"time"

"github.com/rs/zerolog"

"github.com/MontFerret/ferret"
"github.com/MontFerret/ferret/pkg/drivers/cdp"
Expand All @@ -21,6 +26,129 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/logging"
)

type (
Timer struct {
start time.Time
end time.Time
}

Profiler struct {
labels []string
timers map[string]*Timer
allocs map[string]*rt.MemStats
cpus map[string]*bytes.Buffer
heaps map[string]*bytes.Buffer
}
)

func NewProfiler() *Profiler {
return &Profiler{
labels: make([]string, 0, 10),
timers: make(map[string]*Timer),
allocs: make(map[string]*rt.MemStats),
cpus: make(map[string]*bytes.Buffer),
heaps: make(map[string]*bytes.Buffer),
}
}

func (p *Profiler) StartTimer(label string) {
timer := &Timer{
start: time.Now(),
}

p.timers[label] = timer
p.labels = append(p.labels, label)
}

func (p *Profiler) StopTimer(label string) {
timer, found := p.timers[label]

if !found {
panic(fmt.Sprintf("Timer not found: %s", label))
}

timer.end = time.Now()
}

func (p *Profiler) HeapSnapshot(label string) {
heap := &bytes.Buffer{}

err := pprof.WriteHeapProfile(heap)

if err != nil {
panic(err)
}

p.heaps[label] = heap
p.labels = append(p.labels, label)
}

func (p *Profiler) Allocations(label string) {
stats := &rt.MemStats{}

rt.ReadMemStats(stats)

p.allocs[label] = stats
p.labels = append(p.labels, label)
}

func (p *Profiler) StartCPU(label string) {
b := &bytes.Buffer{}

if err := pprof.StartCPUProfile(b); err != nil {
panic(err)
}

p.cpus[label] = b
p.labels = append(p.labels, label)
}

func (p *Profiler) StopCPU() {
pprof.StopCPUProfile()
}

func (p *Profiler) Print(label string) {
writer := &bytes.Buffer{}

timer, found := p.timers[label]

if found {
fmt.Fprintln(writer, fmt.Sprintf("Time: %s", timer.end.Sub(timer.start)))
}

stats, found := p.allocs[label]

if found {
fmt.Fprintln(writer, fmt.Sprintf("Alloc: %s", byteCountDecimal(stats.Alloc)))
fmt.Fprintln(writer, fmt.Sprintf("Frees: %s", byteCountDecimal(stats.Frees)))
fmt.Fprintln(writer, fmt.Sprintf("Total Alloc: %s", byteCountDecimal(stats.TotalAlloc)))
fmt.Fprintln(writer, fmt.Sprintf("Heap Alloc: %s", byteCountDecimal(stats.HeapAlloc)))
fmt.Fprintln(writer, fmt.Sprintf("Heap Sys: %s", byteCountDecimal(stats.HeapSys)))
fmt.Fprintln(writer, fmt.Sprintf("Heap Idle: %s", byteCountDecimal(stats.HeapIdle)))
fmt.Fprintln(writer, fmt.Sprintf("Heap In Use: %s", byteCountDecimal(stats.HeapInuse)))
fmt.Fprintln(writer, fmt.Sprintf("Heap Released: %s", byteCountDecimal(stats.HeapReleased)))
fmt.Fprintln(writer, fmt.Sprintf("Heap Objects: %d", stats.HeapObjects))
}

cpu, found := p.cpus[label]

if found {
fmt.Fprintln(writer, cpu.String())
}

if writer.Len() > 0 {
fmt.Println(fmt.Sprintf("%s:", label))
fmt.Println("-----")
fmt.Println(writer.String())
}
}

func (p *Profiler) PrintAll() {
for _, label := range p.labels {
p.Print(label)
}
}

type Params []string

func (p *Params) String() string {
Expand Down Expand Up @@ -65,6 +193,12 @@ var (
"set CDP address",
)

dryRun = flag.Bool(
"dry-run",
false,
"compiles a given query, but does not execute",
)

logLevel = flag.String(
"log-level",
logging.ErrorLevel.String(),
Expand Down Expand Up @@ -151,7 +285,7 @@ func main() {
}()

if query != "" {
err = execQuery(ctx, engine, opts, query)
err = runQuery(ctx, engine, opts, query)
} else {
err = execFiles(ctx, engine, opts, files)
}
Expand Down Expand Up @@ -228,7 +362,7 @@ func execFiles(ctx context.Context, engine *ferret.Instance, opts []runtime.Opti

log.Debug().Msg("successfully read file")
log.Debug().Msg("executing file...")
err = execQuery(ctx, engine, opts, string(out))
err = runQuery(ctx, engine, opts, string(out))

if err != nil {
log.Debug().Err(err).Msg("failed to execute file")
Expand All @@ -253,6 +387,14 @@ func execFiles(ctx context.Context, engine *ferret.Instance, opts []runtime.Opti
return nil
}

func runQuery(ctx context.Context, engine *ferret.Instance, opts []runtime.Option, query string) error {
if !(*dryRun) {
return execQuery(ctx, engine, opts, query)
}

return analyzeQuery(ctx, engine, opts, query)
}

func execQuery(ctx context.Context, engine *ferret.Instance, opts []runtime.Option, query string) error {
out, err := engine.Exec(ctx, query, opts...)

Expand All @@ -264,3 +406,48 @@ func execQuery(ctx context.Context, engine *ferret.Instance, opts []runtime.Opti

return nil
}

func analyzeQuery(ctx context.Context, engine *ferret.Instance, opts []runtime.Option, query string) error {
compilation := "Compilation"
beforeCompilation := "Before compilation"
afterCompilation := "After compilation"
prof := NewProfiler()

prof.Allocations(beforeCompilation)
prof.StartTimer(compilation)
program := engine.MustCompile(query)

prof.StopTimer(compilation)
prof.Allocations(afterCompilation)

exec := "Execution"
beforeExec := "Before execution"
afterExec := "After execution"

prof.Allocations(beforeExec)
prof.StartTimer(exec)

engine.MustRun(ctx, program, opts...)

prof.StopTimer(exec)
prof.Allocations(afterExec)

prof.PrintAll()

return nil
}

func byteCountDecimal(b uint64) string {
const unit = 1000

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 %cB", float64(b)/float64(div), "kMGTPE"[exp])
}
24 changes: 21 additions & 3 deletions ferret.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ func (i *Instance) Exec(ctx context.Context, query string, opts ...runtime.Optio
return nil, err
}

for _, drv := range i.drivers.GetAll() {
ctx = drivers.WithContext(ctx, drv)
}
ctx = i.drivers.WithContext(ctx)

return p.Run(ctx, opts...)
}
Expand All @@ -62,3 +60,23 @@ func (i *Instance) MustExec(ctx context.Context, query string, opts ...runtime.O

return out
}

func (i *Instance) Run(ctx context.Context, program *runtime.Program, opts ...runtime.Option) ([]byte, error) {
if program == nil {
return nil, core.Error(core.ErrInvalidArgument, "program")
}

ctx = i.drivers.WithContext(ctx)

return program.Run(ctx, opts...)
}

func (i *Instance) MustRun(ctx context.Context, program *runtime.Program, opts ...runtime.Option) []byte {
out, err := i.Run(ctx, program, opts...)

if err != nil {
panic(err)
}

return out
}
1 change: 1 addition & 0 deletions pkg/compiler/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ var (
ErrVariableNotFound = errors.New("variable not found")
ErrVariableNotUnique = errors.New("variable is already defined")
ErrInvalidToken = errors.New("invalid token")
ErrUnexpectedToken = errors.New("unexpected token")
ErrInvalidDataSource = errors.New("invalid data source")
)
Loading

0 comments on commit bd6463f

Please sign in to comment.