<file_sep>package cmd

import (
	"fmt"
	"os"
	"strings"

	goflag "flag"

	"github.com/golang/glog"
	"github.com/metral/memhog/pkg/memhog"
	"github.com/spf13/cobra"
)

var (
	cmdName = "memhog"
	usage   = fmt.Sprintf("%s -c config.toml", cmdName)
)

// Define a type for the options of MemHog
type MemHogOptions struct {
	ConfigFile string
}

func AddConfigFileFlag(cmd *cobra.Command, value *string) {
	cmd.Flags().StringVarP(value, "config", "c", *value, "<insert description>")
}

// Fatal prints the message (if provided) and then exits. If V(2) or greater,
// glog.Fatal is invoked for extended information.
func fatal(msg string) {
	if glog.V(2) {
		glog.FatalDepth(2, msg)
	}
	if len(msg) > 0 {
		// add newline if needed
		if !strings.HasSuffix(msg, "\n") {
			msg += "\n"
		}
		fmt.Fprint(os.Stderr, msg)
	}
	os.Exit(1)
}

// NewCmdOptions creates an options Cobra command to return usage
func NewCmdOptions() *cobra.Command {
	cmd := &cobra.Command{
		Use: "options",
		Run: func(cmd *cobra.Command, args []string) {
			cmd.Usage()
		},
	}

	return cmd
}

// Create a new command for the memhog. This cmd includes logging,
// and cmd option parsing from flags.
func NewCmdMemHog() (*cobra.Command, error) {
	// Define the options for MemHog command
	options := MemHogOptions{}

	// Create a new command
	cmd := &cobra.Command{
		Use:   usage,
		Short: "",
		Run: func(cmd *cobra.Command, args []string) {
			checkErr(RunMemHog(cmd, &options), fatal)
		},
	}

	// Bind & parse flags defined by external projects.
	// e.g. This imports the golang/glog pkg flags into the cmd flagset
	cmd.Flags().AddGoFlagSet(goflag.CommandLine)
	goflag.CommandLine.Parse([]string{})

	// Define the flags allowed in this command & store each option provided
	// as a flag, into the MemHogOptions
	AddConfigFileFlag(cmd, &options.ConfigFile)

	return cmd, nil
}

func checkErr(err error, handleErr func(string)) {
	if err == nil {
		return
	}
	handleErr(err.Error())
}

// Run the memhog
func RunMemHog(cmd *cobra.Command, options *MemHogOptions) error {
	return run(options)
}

func run(options *MemHogOptions) error {
	// Create a new RAM memhog
	bufferLen := 20
	mh, err := memhog.NewMemHog(bufferLen)
	if err != nil {
		return err
	}

	// Create a slice of buffers and randomly alloc its elements with data to
	// increase & throttle RAM usage
	mh.HogMemory()

	return nil
}

// Print & log a message
func log(msg string) {
	glog.V(2).Infof("%s", msg)
}
<file_sep>FROM alpine:3.6

# Install base packages
RUN apk update && apk upgrade && \
    apk add --no-cache bash coreutils && \
    echo -ne "Alpine Linux v3.6 image. (`uname -rsv`)\n" >> /.built && cat /.built

ADD _output/memhog/linux_amd64/memhog /

ENTRYPOINT ["/memhog"]
<file_sep>## memhog

An app that wastefully allocates memory.

It is used in tandem with [memhog-operator](https://github.com/metral/memhog-operator) to demo a simple vertical auto-scaler based on memory consumption.

## Requirements
* Kubernetes v1.7.0+
* glide v0.11.1

### Building & Running

```
// Build
$ glide install -s -u -v
$ make
```

#### Run Locally

```
$ $GOPATH/bin/memhog -v2
```

#### Run on Kubernetes

```
// Create AppMonitor for Pod
$ kubectl create -f k8s/appmonitor/johnny-cache.yaml

$ kubectl create -f k8s/pod/memhog.yaml
```

Or in one command:

```
$ kubectl create -f k8s/ -R
```
<file_sep>package memhog

import (
	"fmt"
	"math/rand"
	"runtime"
	"time"
)

func init() {
	// Seed the rand generator
	rand.Seed(time.Now().UnixNano())
}

// MemHog is an object that can hold data that is used to throttle RAM
type MemHog struct {
	Buffer [][]byte
}

// Return a []byte of size between range (min, max) in MiB
func makeBuffer(min, max int) []byte {
	i := min * 1048576
	j := max * 1048576
	return make([]byte, rand.Intn(j-i)+i)
}

func NewMemHog(bufferLen int) (*MemHog, error) {
	// Create a slice of byte slices with length: bufferSize
	b := make([][]byte, bufferLen)

	// Return a new MemHog
	return &MemHog{
		Buffer: b,
	}, nil
}

// Create a slice of buffers and randomly alloc its elements with data to
// increase & throttle RAM usage
func (t *MemHog) HogMemory() {
	var m runtime.MemStats
	bufferLen := len(t.Buffer)

	loops := 0
	for {
		loops += 1

		// Make a new buffer between 5-10 MiB, and set it into a
		// random index in the slice of buffers.
		// Any non-nil buffers that may be replaced by a new random buffer
		// essentially become disregarded, awaiting to be cleaned up by go's GC.
		i := rand.Intn(bufferLen)
		t.Buffer[i] = makeBuffer(5, 10)

		time.Sleep(1 * time.Second)

		bytes := 0
		for i := 0; i < bufferLen; i++ {
			if t.Buffer[i] != nil {
				// Compute the total bytes of the slice of buffers
				bytes += len(t.Buffer[i])
			}
		}

		// Print mem stats
		div := uint64(1048576) // 1048576 bytes = 1 MiB
		runtime.ReadMemStats(&m)
		fmt.Printf("heap: %d MiB\nbuffer_size: %d MiB\nheap_alloc: %d MiB\n"+
			"heap_idle: %d MiB\nheap_released: %d MiB\nloops: %d\n\n",
			m.HeapSys/div, bytes/int(div), m.HeapAlloc/div,
			m.HeapIdle/div, m.HeapReleased/div, loops)
	}
}
