Export metrics to a file (#122)
This commit is contained in:
parent
e63a886f04
commit
d78b6cdc44
101
cmd/analyze.go
101
cmd/analyze.go
@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
@ -8,6 +9,7 @@ import (
|
||||
"github.com/wagoodman/dive/image"
|
||||
"github.com/wagoodman/dive/ui"
|
||||
"github.com/wagoodman/dive/utils"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// doAnalyzeCmd takes a docker image tag, digest, or id and displays the
|
||||
@ -32,14 +34,84 @@ func doAnalyzeCmd(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
utils.Exit(1)
|
||||
}
|
||||
color.New(color.Bold).Println("Analyzing Image")
|
||||
result := fetchAndAnalyze(userImage)
|
||||
|
||||
fmt.Println(" Building cache...")
|
||||
cache := filetree.NewFileTreeCache(result.RefTrees)
|
||||
cache.Build()
|
||||
run(userImage)
|
||||
}
|
||||
|
||||
ui.Run(result, cache)
|
||||
type export struct {
|
||||
Layer []exportLayer `json:"layer"`
|
||||
Image exportImage `json:"image"`
|
||||
}
|
||||
|
||||
type exportLayer struct {
|
||||
Index int `json:"index"`
|
||||
DigestID string `json:"digestId"`
|
||||
SizeBytes uint64 `json:"sizeBytes"`
|
||||
Command string `json:"command"`
|
||||
}
|
||||
type exportImage struct {
|
||||
SizeBytes uint64 `json:"sizeBytes"`
|
||||
InefficientBytes uint64 `json:"inefficientBytes"`
|
||||
EfficiencyScore float64 `json:"efficiencyScore"`
|
||||
InefficientFiles []inefficientFiles `json:"inefficientFiles"`
|
||||
}
|
||||
|
||||
type inefficientFiles struct {
|
||||
Count int `json:"count"`
|
||||
SizeBytes uint64 `json:"sizeBytes"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
||||
func newExport(analysis *image.AnalysisResult) *export {
|
||||
data := export{}
|
||||
data.Layer = make([]exportLayer, len(analysis.Layers))
|
||||
data.Image.InefficientFiles = make([]inefficientFiles, len(analysis.Inefficiencies))
|
||||
|
||||
// export layers in order
|
||||
for revIdx := len(analysis.Layers) - 1; revIdx >= 0; revIdx-- {
|
||||
layer := analysis.Layers[revIdx]
|
||||
idx := (len(analysis.Layers) - 1) - revIdx
|
||||
|
||||
data.Layer[idx] = exportLayer{
|
||||
Index: idx,
|
||||
DigestID: layer.Id(),
|
||||
SizeBytes: layer.Size(),
|
||||
Command: layer.Command(),
|
||||
}
|
||||
}
|
||||
|
||||
// export image info
|
||||
data.Image.SizeBytes = 0
|
||||
for idx := 0; idx < len(analysis.Layers); idx++ {
|
||||
data.Image.SizeBytes += analysis.Layers[idx].Size()
|
||||
}
|
||||
|
||||
data.Image.EfficiencyScore = analysis.Efficiency
|
||||
|
||||
for idx := 0; idx < len(analysis.Inefficiencies); idx++ {
|
||||
fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx]
|
||||
data.Image.InefficientBytes += uint64(fileData.CumulativeSize)
|
||||
|
||||
data.Image.InefficientFiles[idx] = inefficientFiles{
|
||||
Count: len(fileData.Nodes),
|
||||
SizeBytes: uint64(fileData.CumulativeSize),
|
||||
File: fileData.Path,
|
||||
}
|
||||
}
|
||||
|
||||
return &data
|
||||
}
|
||||
|
||||
func exportStatistics(analysis *image.AnalysisResult) {
|
||||
data := newExport(analysis)
|
||||
payload, err := json.MarshalIndent(&data, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = ioutil.WriteFile(exportFile, payload, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAndAnalyze(imageID string) *image.AnalysisResult {
|
||||
@ -60,3 +132,20 @@ func fetchAndAnalyze(imageID string) *image.AnalysisResult {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func run(imageID string) {
|
||||
color.New(color.Bold).Println("Analyzing Image")
|
||||
result := fetchAndAnalyze(imageID)
|
||||
|
||||
if exportFile != "" {
|
||||
exportStatistics(result)
|
||||
color.New(color.Bold).Println(fmt.Sprintf("Exported to %s", exportFile))
|
||||
utils.Exit(0)
|
||||
}
|
||||
|
||||
fmt.Println(" Building cache...")
|
||||
cache := filetree.NewFileTreeCache(result.RefTrees)
|
||||
cache.Build()
|
||||
|
||||
ui.Run(result, cache)
|
||||
}
|
||||
|
13
cmd/build.go
13
cmd/build.go
@ -1,12 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wagoodman/dive/filetree"
|
||||
"github.com/wagoodman/dive/ui"
|
||||
"github.com/wagoodman/dive/utils"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -47,12 +43,5 @@ func doBuildCmd(cmd *cobra.Command, args []string) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
color.New(color.Bold).Println("Analyzing Image")
|
||||
result := fetchAndAnalyze(string(imageId))
|
||||
|
||||
fmt.Println(" Building cache...")
|
||||
cache := filetree.NewFileTreeCache(result.RefTrees)
|
||||
cache.Build()
|
||||
|
||||
ui.Run(result, cache)
|
||||
run(string(imageId))
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
var exportFile string
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
@ -42,8 +43,9 @@ func init() {
|
||||
cobra.OnInitialize(initLogging)
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dive.yaml, ~/.config/dive.yaml, or $XDG_CONFIG_HOME/dive.yaml)")
|
||||
|
||||
rootCmd.PersistentFlags().BoolP("version", "v", false, "display version number")
|
||||
|
||||
rootCmd.Flags().StringVarP(&exportFile, "json", "j", "", "Skip the interactive TUI and write the layer analysis statistics to a given file.")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
|
Loading…
x
Reference in New Issue
Block a user