blob: 7d15d817eb927c681ebf58212ea83b1c2eefa878 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
import (
"fmt"
"strings"
"time"
"unicode"
"v.io/v23/context"
"v.io/x/ref/services/ben"
)
type Store interface {
Save(ctx *context.T, scenario ben.Scenario, code ben.SourceCode, uploader string, uploadTime time.Time, runs []ben.Run) error
Benchmarks(query *Query) BenchmarkIterator
Runs(benchmarkID string) (Benchmark, RunIterator)
DescribeSource(id string) (ben.SourceCode, error)
}
type Query struct {
Name string
CPU string
OS string
Uploader string
Label string
}
// Benchmark identifies a particular (Benchmark, Scenario, Uploader) tuple.
// Many ben.Run objects are associated with a single Benchmark.
type Benchmark struct {
ID string // A unique identifier of this particular Benchmark
Name string // Name (e.g. v.io/v23/security.BenchmarkSign) of the Benchmark.
Scenario ben.Scenario // The scenario under which the benchmark was run.
Uploader string // Identity of the user that uploaded the results.
// Results from most recently uploaded runs for this benchmark.
NanoSecsPerOp float64
MegaBytesPerSec float64
LastUpdate time.Time
}
func (b Benchmark) PrettyTime() string {
if b.NanoSecsPerOp < 100 {
return fmt.Sprintf("%vns", b.NanoSecsPerOp)
}
return time.Duration(int64(b.NanoSecsPerOp)).String()
}
type Iterator interface {
Advance() bool
Err() error
Close()
}
type BenchmarkIterator interface {
Iterator
Value() Benchmark
Runs() RunIterator
}
type RunIterator interface {
Iterator
Value() (run ben.Run, sourceCodeID string, uploadTime time.Time)
}
func (q *Query) String() string {
var fields []string
quot := func(in string) string {
if strings.ContainsRune(in, ' ') {
return fmt.Sprintf("%q", in)
}
return in
}
if len(q.Name) > 0 {
fields = append(fields, quot(q.Name))
}
if len(q.CPU) > 0 {
fields = append(fields, "cpu:"+quot(q.CPU))
}
if len(q.OS) > 0 {
fields = append(fields, "os:"+quot(q.OS))
}
if len(q.Uploader) > 0 {
fields = append(fields, "uploader:"+quot(q.Uploader))
}
if len(q.Label) > 0 {
fields = append(fields, "label:"+quot(q.Label))
}
return strings.Join(fields, " ")
}
// ParseQuery converts a query string into a structured Query object.
//
// The query language supports setting each field in the Query object at most
// once and the query will be an AND of all terms. A more expressive query
// language is left as an excercise to a future enthusiast.
func ParseQuery(query string) (*Query, error) {
tokens, err := split(query)
if err != nil {
return nil, err
}
var ret Query
for _, f := range tokens {
var err error
var set bool
set = set || trySetField(&ret.CPU, &err, "cpu:", f)
set = set || trySetField(&ret.OS, &err, "os:", f)
set = set || trySetField(&ret.Uploader, &err, "uploader:", f)
set = set || trySetField(&ret.Label, &err, "label:", f)
set = set || trySetField(&ret.Name, &err, "", f)
if err != nil {
return nil, err
}
}
return &ret, nil
}
func trySetField(dst *string, err *error, prefix, value string) bool {
if *err != nil {
return false
}
if len(value) < len(prefix) || strings.ToLower(value[:len(prefix)]) != prefix {
return false
}
if len(*dst) > 0 {
*err = fmt.Errorf("operator %q already set", prefix)
}
*dst = strings.TrimSuffix(strings.TrimPrefix(value[len(prefix):], `"`), `"`)
return true
}
// split is liked strings.Split except that it doesn't break quoted strings.
func split(input string) ([]string, error) {
if len(input) == 0 {
return nil, nil
}
var (
tokens []string
start int
inq bool
commit = func(end int) {
if end > start {
tokens = append(tokens, input[start:end])
}
}
)
for idx, r := range input {
switch {
case r == '"' && !inq:
inq = true
case r == '"' && inq:
commit(idx + 1)
start = idx + 1
inq = false
case unicode.IsSpace(r) && !inq:
commit(idx)
start = idx + 1
}
}
if inq {
return tokens, fmt.Errorf("unterminated quote")
}
commit(len(input))
return tokens, nil
}