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

timecraft: use wazero compilation cache #38

Merged
merged 4 commits into from
May 31, 2023
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
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
Loading