Skip to content
This repository has been archived by the owner on Feb 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #38 from stealthrocket/wazero-cache
Browse files Browse the repository at this point in the history
timecraft: use wazero compilation cache
  • Loading branch information
achille-roussel committed May 31, 2023
2 parents 3fa01f8 + cec2e4a commit de8d5ff
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 73 deletions.
157 changes: 142 additions & 15 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/stealthrocket/timecraft/internal/object"
"github.com/stealthrocket/timecraft/internal/print/human"
"github.com/stealthrocket/timecraft/internal/timemachine"
"github.com/tetratelabs/wazero"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -139,15 +140,77 @@ func config(ctx context.Context, args []string) error {
}
}

type nullable[T any] struct {
value T
exist bool
}

// Note: commented to satisfy the linter, uncomment if we need it
//
// func null[T any]() nullable[T] {
// return nullable[T]{exist: false}
// }

func value[T any](v T) nullable[T] {
return nullable[T]{value: v, exist: true}
}

func (v *nullable[T]) Value() (T, bool) {
return v.value, v.exist
}

func (v *nullable[T]) MarshalJSON() ([]byte, error) {
if !v.exist {
return []byte("null"), nil
}
return json.Marshal(v.value)
}

func (v *nullable[T]) MarshalYAML() (any, error) {
if !v.exist {
return nil, nil
}
return v.value, nil
}

func (v *nullable[T]) UnmarshalJSON(b []byte) error {
if string(b) == "null" {
v.exist = false
return nil
} else if err := json.Unmarshal(b, &v.value); err != nil {
v.exist = false
return err
} else {
v.exist = true
return nil
}
}

func (v *nullable[T]) UnmarshalYAML(node *yaml.Node) error {
if node.Value == "" || node.Value == "~" || node.Value == "null" {
v.exist = false
return nil
} else if err := node.Decode(&v.value); err != nil {
v.exist = false
return err
} else {
v.exist = true
return nil
}
}

type configuration struct {
Registry struct {
Location string `json:"location"`
Location nullable[human.Path] `json:"location"`
} `json:"registry"`
Cache struct {
Location nullable[human.Path] `json:"location"`
} `json:"cache"`
}

func defaultConfig() *configuration {
c := new(configuration)
c.Registry.Location = "~/.timecraft"
c.Registry.Location = value[human.Path]("~/.timecraft/registry")
return c
}

Expand Down Expand Up @@ -187,27 +250,75 @@ func readConfig(r io.Reader) (*configuration, error) {
return c, nil
}

func (c *configuration) createRegistry() (*timemachine.Registry, error) {
p, err := human.Path(c.Registry.Location).Resolve()
if err != nil {
return nil, err
func (c *configuration) newRuntime(ctx context.Context) wazero.Runtime {
config := wazero.NewRuntimeConfig()

var cache wazero.CompilationCache
if cachePath, ok := c.Cache.Location.Value(); ok {
// The cache is an optimization, so if we encounter errors we notify the
// user but still go ahead with the runtime instantiation.
path, err := cachePath.Resolve()
if err != nil {
fmt.Fprintf(os.Stderr, "ERR: resolving timecraft cache location: %s\n", err)
} else {
cache, err = createCacheDirectory(path)
if err != nil {
fmt.Fprintf(os.Stderr, "ERR: creating timecraft cache directory: %s\n", err)
} else {
config = config.WithCompilationCache(cache)
}
}
}
if err := os.Mkdir(filepath.Dir(p), 0777); err != nil {
if !errors.Is(err, fs.ErrExist) {

runtime := wazero.NewRuntimeWithConfig(ctx, config)
if cache != nil {
runtime = &runtimeWithCompilationCache{
Runtime: runtime,
cache: cache,
}
}
return runtime
}

type runtimeWithCompilationCache struct {
wazero.Runtime
cache wazero.CompilationCache
}

func (r *runtimeWithCompilationCache) Close(ctx context.Context) error {
if r.cache != nil {
defer r.cache.Close(ctx)
}
return r.Runtime.Close(ctx)
}

func (c *configuration) createRegistry() (*timemachine.Registry, error) {
location, ok := c.Registry.Location.Value()
if ok {
path, err := location.Resolve()
if err != nil {
return nil, err
}
if err := createDirectory(path); err != nil {
return nil, err
}
}
return c.openRegistry()
}

func (c *configuration) openRegistry() (*timemachine.Registry, error) {
p, err := human.Path(c.Registry.Location).Resolve()
if err != nil {
return nil, err
}
store, err := object.DirStore(p)
if err != nil {
return nil, err
store := object.EmptyStore()
location, ok := c.Registry.Location.Value()
if ok {
path, err := location.Resolve()
if err != nil {
return nil, err
}
dir, err := object.DirStore(path)
if err != nil {
return nil, err
}
store = dir
}
registry := &timemachine.Registry{
Store: store,
Expand All @@ -225,3 +336,19 @@ func createTempFile(path string, r io.Reader) (string, error) {
_, err = io.Copy(w, r)
return w.Name(), err
}

func createDirectory(path string) error {
if err := os.MkdirAll(path, 0777); err != nil {
if !errors.Is(err, fs.ErrExist) {
return err
}
}
return nil
}

func createCacheDirectory(path string) (wazero.CompilationCache, error) {
if err := createDirectory(path); err != nil {
return nil, err
}
return wazero.NewCompilationCacheWithDir(path)
}
67 changes: 23 additions & 44 deletions describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/stealthrocket/timecraft/internal/print/yamlprint"
"github.com/stealthrocket/timecraft/internal/stream"
"github.com/stealthrocket/timecraft/internal/timemachine"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"golang.org/x/exp/slices"
)
Expand Down Expand Up @@ -82,7 +81,7 @@ func describe(ctx context.Context, args []string) error {
return err
}

var lookup func(context.Context, *timemachine.Registry, string) (any, error)
var lookup func(context.Context, *timemachine.Registry, string, *configuration) (any, error)
var writer stream.WriteCloser[any]
switch output {
case "json":
Expand All @@ -98,43 +97,23 @@ func describe(ctx context.Context, args []string) error {
defer writer.Close()

readers := make([]stream.Reader[any], len(resourceIDs))
for i, resource := range resourceIDs {
readers[i] = &describeResourceReader{
context: ctx,
registry: registry,
resource: resource,
lookup: lookup,
}
for i := range resourceIDs {
resource := resourceIDs[i]
readers[i] = stream.ReaderFunc(func(values []any) (int, error) {
v, err := lookup(ctx, registry, resource, config)
if err != nil {
return 0, err
}
values[0] = v
return 1, io.EOF
})
}

_, err = stream.Copy[any](writer, stream.MultiReader[any](readers...))
return err
}

type describeResourceReader struct {
context context.Context
registry *timemachine.Registry
resource string
lookup func(context.Context, *timemachine.Registry, string) (any, error)
}

func (r *describeResourceReader) Read(values []any) (int, error) {
if r.registry == nil {
return 0, io.EOF
}
if len(values) == 0 {
return 0, nil
}
defer func() { r.registry = nil }()
v, err := r.lookup(r.context, r.registry, r.resource)
if err != nil {
return 0, err
}
values[0] = v
return 1, io.EOF
}

func describeConfig(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func describeConfig(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
d, err := reg.LookupDescriptor(ctx, format.ParseHash(id))
if err != nil {
return nil, err
Expand Down Expand Up @@ -173,7 +152,7 @@ func describeConfig(ctx context.Context, reg *timemachine.Registry, id string) (
return desc, nil
}

func describeModule(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func describeModule(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
d, err := reg.LookupDescriptor(ctx, format.ParseHash(id))
if err != nil {
return nil, err
Expand All @@ -183,7 +162,7 @@ func describeModule(ctx context.Context, reg *timemachine.Registry, id string) (
return nil, err
}

runtime := wazero.NewRuntime(ctx)
runtime := config.newRuntime(ctx)
defer runtime.Close(ctx)

compiledModule, err := runtime.CompileModule(ctx, m.Code)
Expand Down Expand Up @@ -237,7 +216,7 @@ func describeModule(ctx context.Context, reg *timemachine.Registry, id string) (
return desc, nil
}

func describeProcess(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func describeProcess(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
processID, _, p, err := lookupProcessByLogID(ctx, reg, id)
if err != nil {
return nil, err
Expand Down Expand Up @@ -297,7 +276,7 @@ func describeProcess(ctx context.Context, reg *timemachine.Registry, id string)
return desc, nil
}

func describeProfile(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func describeProfile(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
d, err := reg.LookupDescriptor(ctx, format.ParseHash(id))
if err != nil {
return nil, err
Expand All @@ -315,7 +294,7 @@ func describeProfile(ctx context.Context, reg *timemachine.Registry, id string)
return desc, nil
}

func describeRuntime(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func describeRuntime(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
d, err := reg.LookupDescriptor(ctx, format.ParseHash(id))
if err != nil {
return nil, err
Expand All @@ -332,27 +311,27 @@ func describeRuntime(ctx context.Context, reg *timemachine.Registry, id string)
return desc, nil
}

func lookupConfig(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func lookupConfig(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
return lookup(ctx, reg, id, (*timemachine.Registry).LookupConfig)
}

func lookupModule(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func lookupModule(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
return lookup(ctx, reg, id, (*timemachine.Registry).LookupModule)
}

func lookupProcess(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func lookupProcess(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
_, desc, proc, err := lookupProcessByLogID(ctx, reg, id)
if err != nil {
return nil, err
}
return descriptorAndData(desc, proc), nil
}

func lookupProfile(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func lookupProfile(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
return lookup(ctx, reg, id, (*timemachine.Registry).LookupProfile)
}

func lookupRuntime(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func lookupRuntime(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
return lookup(ctx, reg, id, (*timemachine.Registry).LookupRuntime)
}

Expand Down Expand Up @@ -709,7 +688,7 @@ func (desc *logDescriptor) Format(w fmt.State, _ rune) {
_, _ = table.Write(desc.Segments)
}

func describeLog(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
func describeLog(ctx context.Context, reg *timemachine.Registry, id string, config *configuration) (any, error) {
logSegmentNumber := -1
logID, logNumber, ok := strings.Cut(id, "/")
if ok {
Expand Down
4 changes: 2 additions & 2 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ type resource struct {
alt []string
mediaType format.MediaType
get func(context.Context, io.Writer, *timemachine.Registry, bool) stream.WriteCloser[*format.Descriptor]
describe func(context.Context, *timemachine.Registry, string) (any, error)
lookup func(context.Context, *timemachine.Registry, string) (any, error)
describe func(context.Context, *timemachine.Registry, string, *configuration) (any, error)
lookup func(context.Context, *timemachine.Registry, string, *configuration) (any, error)
}

var resources = [...]resource{
Expand Down
10 changes: 10 additions & 0 deletions internal/stream/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ func ReadAll[T any](r Reader[T]) ([]T, error) {
}
}

func ReaderFunc[T any](f func([]T) (int, error)) Reader[T] {
return readerFunc[T](f)
}

type readerFunc[T any] func([]T) (int, error)

func (f readerFunc[T]) Read(values []T) (int, error) {
return f(values)
}

// Writer is an interface implemented by types that write a stream of values of
// type T.
type Writer[T any] interface {
Expand Down
Loading

0 comments on commit de8d5ff

Please sign in to comment.