Initial commit

This commit is contained in:
Ria Bhatia
2017-12-04 13:32:57 -06:00
committed by Erik St. Martin
commit 0075e5b0f3
9056 changed files with 2523100 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
package formatter
import (
"strings"
)
const (
imageHeader = "IMAGE"
createdSinceHeader = "CREATED"
createdAtHeader = "CREATED AT"
sizeHeader = "SIZE"
labelsHeader = "LABELS"
nameHeader = "NAME"
driverHeader = "DRIVER"
scopeHeader = "SCOPE"
)
type subContext interface {
FullHeader() string
AddHeader(header string)
}
// HeaderContext provides the subContext interface for managing headers
type HeaderContext struct {
header []string
}
// FullHeader returns the header as a string
func (c *HeaderContext) FullHeader() string {
if c.header == nil {
return ""
}
return strings.Join(c.header, "\t")
}
// AddHeader adds another column to the header
func (c *HeaderContext) AddHeader(header string) {
if c.header == nil {
c.header = []string{}
}
c.header = append(c.header, strings.ToUpper(header))
}
func stripNamePrefix(ss []string) []string {
sss := make([]string, len(ss))
for i, s := range ss {
sss[i] = s[1:]
}
return sss
}

View File

@@ -0,0 +1,123 @@
package formatter
import (
"bytes"
"fmt"
"io"
"strings"
"text/tabwriter"
"text/template"
"github.com/hyperhq/hypercli/utils/templates"
)
// Format keys used to specify certain kinds of output formats
const (
TableFormatKey = "table"
RawFormatKey = "raw"
PrettyFormatKey = "pretty"
defaultQuietFormat = "{{.ID}}"
)
// Format is the format string rendered using the Context
type Format string
// IsTable returns true if the format is a table-type format
func (f Format) IsTable() bool {
return strings.HasPrefix(string(f), TableFormatKey)
}
// Contains returns true if the format contains the substring
func (f Format) Contains(sub string) bool {
return strings.Contains(string(f), sub)
}
// Context contains information required by the formatter to print the output as desired.
type Context struct {
// Output is the output stream to which the formatted string is written.
Output io.Writer
// Format is used to choose raw, table or custom format for the output.
Format Format
// Trunc when set to true will truncate the output of certain fields such as Container ID.
Trunc bool
// internal element
finalFormat string
header string
buffer *bytes.Buffer
}
func (c *Context) preFormat() {
c.finalFormat = string(c.Format)
// TODO: handle this in the Format type
if c.Format.IsTable() {
c.finalFormat = c.finalFormat[len(TableFormatKey):]
}
c.finalFormat = strings.Trim(c.finalFormat, " ")
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
c.finalFormat = r.Replace(c.finalFormat)
}
func (c *Context) parseFormat() (*template.Template, error) {
tmpl, err := templates.Parse(c.finalFormat)
if err != nil {
return tmpl, fmt.Errorf("Template parsing error: %v\n", err)
}
return tmpl, err
}
func (c *Context) postFormat(tmpl *template.Template, subContext subContext) {
if c.Format.IsTable() {
if len(c.header) == 0 {
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
tmpl.Execute(bytes.NewBufferString(""), subContext)
c.header = subContext.FullHeader()
}
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
t.Write([]byte(c.header))
t.Write([]byte("\n"))
c.buffer.WriteTo(t)
t.Flush()
} else {
c.buffer.WriteTo(c.Output)
}
}
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
if err := tmpl.Execute(c.buffer, subContext); err != nil {
return fmt.Errorf("Template parsing error: %v\n", err)
}
if c.Format.IsTable() && len(c.header) == 0 {
c.header = subContext.FullHeader()
}
c.buffer.WriteString("\n")
return nil
}
// SubFormat is a function type accepted by Write()
type SubFormat func(func(subContext) error) error
// Write the template to the buffer using this Context
func (c *Context) Write(sub subContext, f SubFormat) error {
c.buffer = bytes.NewBufferString("")
c.preFormat()
tmpl, err := c.parseFormat()
if err != nil {
return err
}
subFormat := func(subContext subContext) error {
return c.contextFormat(tmpl, subContext)
}
if err := f(subFormat); err != nil {
return err
}
c.postFormat(tmpl, sub)
return nil
}

View File

@@ -0,0 +1,177 @@
package formatter
import (
"fmt"
"sync"
units "github.com/docker/go-units"
)
const (
defaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}"
containerHeader = "CONTAINER"
cpuPercHeader = "CPU %"
netIOHeader = "NET I/O"
blockIOHeader = "BLOCK I/O"
memPercHeader = "MEM %"
memUseHeader = "MEM USAGE / LIMIT"
)
// StatsEntry represents represents the statistics data collected from a container
type StatsEntry struct {
Container string
Name string
ID string
CPUPercentage float64
Memory float64
MemoryLimit float64
MemoryPercentage float64
NetworkRx float64
NetworkTx float64
BlockRead float64
BlockWrite float64
IsInvalid bool
}
// ContainerStats represents an entity to store containers statistics synchronously
type ContainerStats struct {
mutex sync.Mutex
StatsEntry
err error
}
// GetError returns the container statistics error.
// This is used to determine whether the statistics are valid or not
func (cs *ContainerStats) GetError() error {
cs.mutex.Lock()
defer cs.mutex.Unlock()
return cs.err
}
// SetErrorAndReset zeroes all the container statistics and store the error.
// It is used when receiving time out error during statistics collecting to reduce lock overhead
func (cs *ContainerStats) SetErrorAndReset(err error) {
cs.mutex.Lock()
defer cs.mutex.Unlock()
cs.CPUPercentage = 0
cs.Memory = 0
cs.MemoryPercentage = 0
cs.MemoryLimit = 0
cs.NetworkRx = 0
cs.NetworkTx = 0
cs.BlockRead = 0
cs.BlockWrite = 0
cs.err = err
cs.IsInvalid = true
}
// SetError sets container statistics error
func (cs *ContainerStats) SetError(err error) {
cs.mutex.Lock()
defer cs.mutex.Unlock()
cs.err = err
if err != nil {
cs.IsInvalid = true
}
}
// SetStatistics set the container statistics
func (cs *ContainerStats) SetStatistics(s StatsEntry) {
cs.mutex.Lock()
defer cs.mutex.Unlock()
s.Container = cs.Container
cs.StatsEntry = s
}
// GetStatistics returns container statistics with other meta data such as the container name
func (cs *ContainerStats) GetStatistics() StatsEntry {
cs.mutex.Lock()
defer cs.mutex.Unlock()
return cs.StatsEntry
}
// NewStatsFormat returns a format for rendering an CStatsContext
func NewStatsFormat(source string) Format {
if source == TableFormatKey {
return Format(defaultStatsTableFormat)
}
return Format(source)
}
// NewContainerStats returns a new ContainerStats entity and sets in it the given name
func NewContainerStats(container string) *ContainerStats {
return &ContainerStats{
StatsEntry: StatsEntry{Container: container},
}
}
// ContainerStatsWrite renders the context for a list of containers statistics
func ContainerStatsWrite(ctx Context, containerStats []StatsEntry) error {
render := func(format func(subContext subContext) error) error {
for _, cstats := range containerStats {
containerStatsCtx := &containerStatsContext{
s: cstats,
}
if err := format(containerStatsCtx); err != nil {
return err
}
}
return nil
}
return ctx.Write(&containerStatsContext{}, render)
}
type containerStatsContext struct {
HeaderContext
s StatsEntry
}
func (c *containerStatsContext) Container() string {
c.AddHeader(containerHeader)
return c.s.Container
}
func (c *containerStatsContext) CPUPerc() string {
c.AddHeader(cpuPercHeader)
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
return fmt.Sprintf("%.2f%%", c.s.CPUPercentage)
}
func (c *containerStatsContext) MemUsage() string {
header := memUseHeader
c.AddHeader(header)
if c.s.IsInvalid {
return fmt.Sprintf("-- / --")
}
return fmt.Sprintf("%s / %s", units.HumanSize(c.s.Memory), units.HumanSize(c.s.MemoryLimit))
}
func (c *containerStatsContext) MemPerc() string {
header := memPercHeader
c.AddHeader(header)
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage)
}
func (c *containerStatsContext) NetIO() string {
c.AddHeader(netIOHeader)
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
return fmt.Sprintf("%s / %s", units.HumanSize(c.s.NetworkRx), units.HumanSize(c.s.NetworkTx))
}
func (c *containerStatsContext) BlockIO() string {
c.AddHeader(blockIOHeader)
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
return fmt.Sprintf("%s / %s", units.HumanSize(c.s.BlockRead), units.HumanSize(c.s.BlockWrite))
}