Skip to content

Commit

Permalink
overhaul ghw-snapshot tool
Browse files Browse the repository at this point in the history
This is the first step in actually overhauling the ghw library's context
package and moving to a more standard `context.Context` usage. In order
to standardize the context usage, we needed to change the way that the
ghw-snapshot functionality worked to manually construct a
`pkg/context.Context` and call `pkg/context.Context:Do()` while reading
the snapshot tarball.

In order to do that, I created a new `ghw-snapshot read` command that
accepts a single argument to the snapshot tarball path to read:

```
➜  ghw git:(overhaul-snapshot) ✗ GHW_DISABLE_WARNINGS=1 go run cmd/ghw-snapshot/main.go read testdata/snapshots/linux-amd64-intel-xeon-L5640.tar.gz
block storage (8 disks, 723GB physical storage)
cpu (2 physical packages, 12 cores, 24 hardware threads)
gpu (0 graphics cards)
memory (66GB physical, 63GB usable)
net (0 NICs)
topology NUMA (2 nodes)
chassis type=unknown vendor=unknown version=unknown
bios vendor=unknown version=unknown
baseboard vendor=unknown version=unknown product=unknown
product family=unknown name=unknown vendor=unknown sku=unknown version=unknown
PCI (82 devices)

➜  ghw git:(overhaul-snapshot) ✗ GHW_DISABLE_WARNINGS=1 go run cmd/ghw-snapshot/main.go read testdata/snapshots/linux-amd64-amd-ryzen-1600.tar.gz
block storage (8 disks, 3TB physical storage)
cpu (1 physical package, 6 cores, 12 hardware threads)
gpu (1 graphics card)
memory (32GB physical, 32GB usable)
net (4 NICs)
topology NUMA (0 nodes)
chassis type=unknown vendor=unknown version=unknown
bios vendor=unknown version=unknown
baseboard vendor=unknown version=unknown product=unknown
product family=unknown name=unknown vendor=unknown sku=unknown version=unknown
PCI (43 devices)
```

A followup series of patches will make the aforementioned changes to
`pkg/context` and remove the `pkg/context.Context:Do()` method and put
that entirely in the `cmd/ghw-snapshot/command/read.go` file which is
the only place we actually use it.

Signed-off-by: Jay Pipes <jaypipes@gmail.com>
  • Loading branch information
jaypipes committed Sep 24, 2024
1 parent 26a61af commit 8a675db
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 88 deletions.
85 changes: 85 additions & 0 deletions cmd/ghw-snapshot/command/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package command

import (
"crypto/md5"
"fmt"
"io"
"os"
"runtime"

"github.com/spf13/cobra"

"github.com/jaypipes/ghw/pkg/snapshot"
)

var (
// output filepath to save snapshot to
outPath string
)

var createCmd = &cobra.Command{
Use: "create",
Short: "Creates a new ghw snapshot",
RunE: doCreate,
}

// doCreate creates a ghw snapshot
func doCreate(cmd *cobra.Command, args []string) error {
scratchDir, err := os.MkdirTemp("", "ghw-snapshot")
if err != nil {
return err
}
defer os.RemoveAll(scratchDir)

snapshot.SetTraceFunction(trace)
if err = snapshot.CloneTreeInto(scratchDir); err != nil {
return err
}

if outPath == "" {
outPath, err = defaultOutPath()
if err != nil {
return err
}
trace("using default output filepath %s\n", outPath)
}

return snapshot.PackFrom(outPath, scratchDir)
}

func systemFingerprint() (string, error) {
hn, err := os.Hostname()
if err != nil {
return "unknown", err
}
m := md5.New()
_, err = io.WriteString(m, hn)
if err != nil {
return "unknown", err
}
return fmt.Sprintf("%x", m.Sum(nil)), nil
}

func defaultOutPath() (string, error) {
fp, err := systemFingerprint()
if err != nil {
return "unknown", err
}
return fmt.Sprintf("%s-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH, fp), nil
}

func init() {
createCmd.PersistentFlags().StringVarP(
&outPath,
"out", "o",
outPath,
"Path to place snapshot. Defaults to file in current directory with name $OS-$ARCH-$HASHSYSTEMNAME.tar.gz",
)
rootCmd.AddCommand(createCmd)
}
47 changes: 47 additions & 0 deletions cmd/ghw-snapshot/command/read.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package command

import (
"errors"
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/jaypipes/ghw"
ghwcontext "github.com/jaypipes/ghw/pkg/context"
)

var readCmd = &cobra.Command{
Use: "read",
Short: "Reads a new ghw snapshot",
RunE: doRead,
}

// doRead reads a ghw snapshot from the input snapshot path argument
func doRead(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("supply a single argument with the filepath to the snapshot you wish to read")
}
inPath := args[0]
if _, err := os.Stat(inPath); err != nil {
return err
}
os.Setenv("GHW_SNAPSHOT_PATH", inPath)
ctx := ghwcontext.New()

return ctx.Do(func() error {
info, err := ghw.Host()
fmt.Println(info.String())
return err
})
}

func init() {
rootCmd.AddCommand(readCmd)
}
66 changes: 66 additions & 0 deletions cmd/ghw-snapshot/command/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package command

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var (
version string

Check failure on line 17 in cmd/ghw-snapshot/command/root.go

View workflow job for this annotation

GitHub Actions / lint

var `version` is unused (unused)
buildHash string

Check failure on line 18 in cmd/ghw-snapshot/command/root.go

View workflow job for this annotation

GitHub Actions / lint

var `buildHash` is unused (unused)
buildDate string

Check failure on line 19 in cmd/ghw-snapshot/command/root.go

View workflow job for this annotation

GitHub Actions / lint

var `buildDate` is unused (unused)
debug bool
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "ghw-snapshot",
Short: "ghw-snapshot - create and read ghw snapshots.",
Long: `
__ __ __
.-----.| |--.--.--.--.______.-----.-----.---.-.-----.-----.| |--.-----.| |_
| _ || | | | |______|__ --| | _ | _ |__ --|| | _ || _|
|___ ||__|__|________| |_____|__|__|___._| __|_____||__|__|_____||____|
|_____| |__|
Create and read ghw snapshots.
https://github.com/jaypipes/ghw
`,
RunE: doCreate,
}

// Execute adds all child commands to the root command and sets flags
// appropriately. This is called by main.main(). It only needs to happen once
// to the rootCmd.
func Execute(v string, bh string, bd string) {
version = v
buildHash = bh
buildDate = bd

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func trace(msg string, args ...interface{}) {
if !debug {
return
}
fmt.Printf(msg, args...)
}

func init() {
rootCmd.PersistentFlags().BoolVar(
&debug, "debug", false, "Enable or disable debug mode",
)
}
96 changes: 8 additions & 88 deletions cmd/ghw-snapshot/main.go
Original file line number Diff line number Diff line change
@@ -1,105 +1,25 @@
//go:build linux
// +build linux

//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package main

import (
"crypto/md5"
"fmt"
"io"
"os"
"runtime"

"github.com/spf13/cobra"

"github.com/jaypipes/ghw/pkg/snapshot"
"github.com/jaypipes/ghw/cmd/ghw-snapshot/command"
)

var (
// show debug output
debug = false
// output filepath to save snapshot to
outPath string
// version of application at compile time (-X 'main.version=$(VERSION)').
version = "(Unknown Version)"
// buildHash GIT hash of application at compile time (-X 'main.buildHash=$(GITCOMMIT)').
buildHash = "No Git-hash Provided."
// buildDate of application at compile time (-X 'main.buildDate=$(BUILDDATE)').
buildDate = "No Build Date Provided."
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "ghw-snapshot",
Short: "ghw-snapshot - Snapshot filesystem containing system information.",
RunE: execute,
}

func trace(msg string, args ...interface{}) {
if !debug {
return
}
fmt.Printf(msg, args...)
}

func systemFingerprint() (string, error) {
hn, err := os.Hostname()
if err != nil {
return "unknown", err
}
m := md5.New()
_, err = io.WriteString(m, hn)
if err != nil {
return "unknown", err
}
return fmt.Sprintf("%x", m.Sum(nil)), nil
}

func defaultOutPath() (string, error) {
fp, err := systemFingerprint()
if err != nil {
return "unknown", err
}
return fmt.Sprintf("%s-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH, fp), nil
}

func execute(cmd *cobra.Command, args []string) error {
scratchDir, err := os.MkdirTemp("", "ghw-snapshot")
if err != nil {
return err
}
defer os.RemoveAll(scratchDir)

snapshot.SetTraceFunction(trace)
if err = snapshot.CloneTreeInto(scratchDir); err != nil {
return err
}

if outPath == "" {
outPath, err = defaultOutPath()
if err != nil {
return err
}
trace("using default output filepath %s\n", outPath)
}

return snapshot.PackFrom(outPath, scratchDir)
}

func main() {
if err := rootCmd.Execute(); err != nil {
trace("execution failed: %v\n", err)
}
}

func init() {
rootCmd.PersistentFlags().StringVarP(
&outPath,
"out", "o",
outPath,
"Path to place snapshot. Defaults to file in current directory with name $OS-$ARCH-$HASHSYSTEMNAME.tar.gz",
)
rootCmd.PersistentFlags().BoolVarP(
&debug, "debug", "d", false, "Enable or disable debug mode",
)
command.Execute(version, buildHash, buildDate)
}

0 comments on commit 8a675db

Please sign in to comment.