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

Commit

Permalink
timecraft: add profile resource
Browse files Browse the repository at this point in the history
Signed-off-by: Achille Roussel <[email protected]>
  • Loading branch information
achille-roussel committed May 30, 2023
1 parent fe71750 commit 43bb77a
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 134 deletions.
1 change: 1 addition & 0 deletions format/timecraft.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const (
TypeTimecraftRuntime MediaType = "application/vnd.timecraft.runtime.v1+json"
TypeTimecraftConfig MediaType = "application/vnd.timecraft.config.v1+json"
TypeTimecraftProcess MediaType = "application/vnd.timecraft.process.v1+json"
TypeTimecraftProfile MediaType = "application/vnd.timecraft.profile.v1+pprof"
TypeTimecraftManifest MediaType = "application/vnd.timecraft.manifest.v1+json"
TypeTimecraftModule MediaType = "application/vnd.timecraft.module.v1+wasm"
)
Expand Down
26 changes: 7 additions & 19 deletions internal/cmd/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,29 +56,13 @@ func describe(ctx context.Context, args []string) error {
flagSet := newFlagSet("timecraft describe", describeUsage)
customVar(flagSet, &output, "o", "output")
customVar(flagSet, &registryPath, "r", "registry")
parseFlags(flagSet, args)
args = parseFlags(flagSet, args)

args = flagSet.Args()
if len(args) == 0 {
return errors.New(`expected one resource id as argument`)
return errors.New(`expected a resource type as argument`)
}
resourceTypeLookup := args[0]
resourceIDs := []string{}
args = args[1:]

for len(args) > 0 {
parseFlags(flagSet, args)
args = flagSet.Args()

i := slices.IndexFunc(args, func(s string) bool {
return strings.HasPrefix(s, "-")
})
if i < 0 {
i = len(args)
}
resourceIDs = append(resourceIDs, args[:i]...)
args = args[i:]
}
resourceIDs := args[1:]

resource, ok := findResource(resourceTypeLookup, resources[:])
if !ok {
Expand Down Expand Up @@ -268,6 +252,10 @@ func describeProcess(ctx context.Context, reg *timemachine.Registry, id string)
return desc, nil
}

func describeProfiles(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
return nil, errors.New("TODO")
}

func describeRuntime(ctx context.Context, reg *timemachine.Registry, id string) (any, error) {
d, err := reg.LookupDescriptor(ctx, format.ParseHash(id))
if err != nil {
Expand Down
60 changes: 52 additions & 8 deletions internal/cmd/get.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bytes"
"context"
"errors"
"fmt"
Expand All @@ -9,6 +10,7 @@ import (
"strings"
"time"

"github.com/google/uuid"
"github.com/stealthrocket/timecraft/format"
"github.com/stealthrocket/timecraft/internal/print/human"
"github.com/stealthrocket/timecraft/internal/print/jsonprint"
Expand All @@ -23,7 +25,7 @@ Usage: timecraft get <resource type> [options]
The get sub-command gives access to the state of the time machine registry.
The command must be followed by the name of resources to display, which must
be one of config, log, module, process, or runtime.
be one of config, log, module, process, profile, or runtime.
(the command also accepts plurals and abbreviations of the resource names)
Examples:
Expand Down Expand Up @@ -89,6 +91,14 @@ var resources = [...]resource{
describe: describeProcess,
lookup: lookupProcess,
},
{
typ: "profile",
alt: []string{"prof", "profs", "profiles"},
mediaType: format.TypeTimecraftProfile,
get: getProfiles,
describe: describeProfiles,
lookup: describeProfiles,
},
{
typ: "runtime",
alt: []string{"rt", "runtimes"},
Expand All @@ -109,15 +119,12 @@ func get(ctx context.Context, args []string) error {
flagSet := newFlagSet("timecraft get", getUsage)
customVar(flagSet, &output, "o", "output")
customVar(flagSet, &registryPath, "r", "registry")
parseFlags(flagSet, args)
args = parseFlags(flagSet, args)

args = flagSet.Args()
if len(args) == 0 {
if len(args) != 1 {
return errors.New(`expected exactly one resource type as argument` + useGet())
}
resourceTypeLookup := args[0]
parseFlags(flagSet, args[1:])

resource, ok := findResource(resourceTypeLookup, resources[:])
if !ok {
matchingResources := findMatchingResources(resourceTypeLookup, resources[:])
Expand Down Expand Up @@ -228,7 +235,7 @@ func getModules(ctx context.Context, w io.Writer, reg *timemachine.Registry) str
func getProcesses(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
type process struct {
ID format.UUID `text:"PROCESS ID"`
StartTime human.Time `text:"STARTED"`
StartTime human.Time `text:"START"`
}
return newTableWriter(w,
func(p1, p2 process) bool {
Expand All @@ -246,6 +253,43 @@ func getProcesses(ctx context.Context, w io.Writer, reg *timemachine.Registry) s
})
}

func getProfiles(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
type profile struct {
ID string `text:"PROFILE ID"`
ProcessID format.UUID `text:"PROCESS ID"`
Type string `text:"TYPE"`
StartTime human.Time `text:"START"`
Duration human.Duration `text:"DURATION"`
Size human.Bytes `text:"SIZE"`
}
return newTableWriter(w,
func(p1, p2 profile) bool {
if p1.ProcessID != p2.ProcessID {
return bytes.Compare(p1.ProcessID[:], p2.ProcessID[:]) < 0
}
if p1.Type != p2.Type {
return p1.Type < p2.Type
}
if !time.Time(p1.StartTime).Equal(time.Time(p2.StartTime)) {
return time.Time(p1.StartTime).Before(time.Time(p2.StartTime))
}
return p1.Duration < p2.Duration
},
func(desc *format.Descriptor) (profile, error) {
processID, _ := uuid.Parse(desc.Annotations["timecraft.process.id"])
startTime, _ := time.Parse(time.RFC3339Nano, desc.Annotations["timecraft.profile.start"])
endTime, _ := time.Parse(time.RFC3339Nano, desc.Annotations["timecraft.profile.end"])
return profile{
ID: desc.Digest.Short(),
ProcessID: processID,
Type: desc.Annotations["timecraft.profile.type"],
StartTime: human.Time(startTime),
Duration: human.Duration(endTime.Sub(startTime)),
Size: human.Bytes(desc.Size),
}, nil
})
}

func getRuntimes(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] {
type runtime struct {
ID string `text:"RUNTIME ID"`
Expand Down Expand Up @@ -273,8 +317,8 @@ func getLogs(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream
type manifest struct {
ProcessID format.UUID `text:"PROCESS ID"`
Segments human.Count `text:"SEGMENTS"`
StartTime human.Time `text:"START"`
Size human.Bytes `text:"SIZE"`
StartTime human.Time `text:"STARTED"`
}
return newTableWriter(w,
func(m1, m2 manifest) bool {
Expand Down
59 changes: 30 additions & 29 deletions internal/cmd/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,36 @@ For a description of each command, run 'timecraft help <command>'.`

func help(ctx context.Context, args []string) error {
flagSet := newFlagSet("timecraft help", helpUsage)
parseFlags(flagSet, args)

var cmd string
var msg string

if args = flagSet.Args(); len(args) > 0 {
cmd = args[0]
args = parseFlags(flagSet, args)

for i, cmd := range args {
var msg string

if i != 0 {
fmt.Println("---")
}

switch cmd {
case "describe":
msg = describeUsage
case "get":
msg = getUsage
case "help", "":
msg = helpUsage
case "profile":
msg = profileUsage
case "run":
msg = runUsage
case "replay":
msg = replayUsage
case "version":
msg = versionUsage
default:
fmt.Printf("timecraft help %s: unknown command\n", cmd)
return ExitCode(1)
}

fmt.Println(msg)
}

switch cmd {
case "describe":
msg = describeUsage
case "get":
msg = getUsage
case "help", "":
msg = helpUsage
case "profile":
msg = profileUsage
case "run":
msg = runUsage
case "replay":
msg = replayUsage
case "version":
msg = versionUsage
default:
fmt.Printf("timecraft help %s: unknown command\n", cmd)
return ExitCode(1)
}

fmt.Println(msg)
return nil
}
Loading

0 comments on commit 43bb77a

Please sign in to comment.