[ARVADOS-DEV] updated: 2ea787126fd985098c5cd3cb19578ee7774ce0db

Git user git at public.arvados.org
Thu Apr 30 14:08:15 UTC 2020


Summary of changes:
 costanalyzer/.gitignore      |   1 +
 costanalyzer/Makefile        |   4 +
 costanalyzer/costanalyzer.go | 505 +++++++++++++++++++++++++++++++++++++++++++
 costanalyzer/go.mod          |   5 +
 costanalyzer/go.sum          | 207 ++++++++++++++++++
 costanalyzer/usage.go        |  20 ++
 6 files changed, 742 insertions(+)
 create mode 100644 costanalyzer/.gitignore
 create mode 100644 costanalyzer/Makefile
 create mode 100644 costanalyzer/costanalyzer.go
 create mode 100644 costanalyzer/go.mod
 create mode 100644 costanalyzer/go.sum
 create mode 100644 costanalyzer/usage.go

       via  2ea787126fd985098c5cd3cb19578ee7774ce0db (commit)
      from  1f416e4347fc8a32dbc5725aa24406cb1b6b2d5d (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 2ea787126fd985098c5cd3cb19578ee7774ce0db
Author: Ward Vandewege <ward at jhvc.com>
Date:   Thu Apr 30 10:07:48 2020 -0400

    Initial commit of the costanalyzer code.
    
    No issue #
    
    Arvados-DCO-1.1-Signed-off-by: Ward Vandewege <ward at jhvc.com>

diff --git a/costanalyzer/.gitignore b/costanalyzer/.gitignore
new file mode 100644
index 0000000..155aa72
--- /dev/null
+++ b/costanalyzer/.gitignore
@@ -0,0 +1 @@
+costanalyzer
diff --git a/costanalyzer/Makefile b/costanalyzer/Makefile
new file mode 100644
index 0000000..a06e92b
--- /dev/null
+++ b/costanalyzer/Makefile
@@ -0,0 +1,4 @@
+
+build:
+	go build -ldflags "-s -w"
+
diff --git a/costanalyzer/costanalyzer.go b/costanalyzer/costanalyzer.go
new file mode 100644
index 0000000..05f4da1
--- /dev/null
+++ b/costanalyzer/costanalyzer.go
@@ -0,0 +1,505 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"git.arvados.org/arvados.git/sdk/go/arvados"
+	"git.arvados.org/arvados.git/sdk/go/arvadosclient"
+	"git.arvados.org/arvados.git/sdk/go/keepclient"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Dict is a helper type so we don't have to write out 'map[string]interface{}' every time.
+type Dict map[string]interface{}
+
+// LegacyNodeInfo is a struct for records created by Arvados Node Manager (Arvados <= 1.4.3)
+// Example:
+// {
+//    "total_cpu_cores":2,
+//    "total_scratch_mb":33770,
+//    "cloud_node":
+//      {
+//        "price":0.1,
+//        "size":"m4.large"
+//      },
+//     "total_ram_mb":7986
+// }
+type LegacyNodeInfo struct {
+	CPUCores  int64           `json:"total_cpu_cores"`
+	ScratchMb int64           `json:"total_scratch_mb"`
+	RAMMb     int64           `json:"total_ram_mb"`
+	CloudNode LegacyCloudNode `json:"cloud_node"`
+}
+
+// LegacyCloudNode is a struct for records created by Arvados Node Manager (Arvados <= 1.4.3)
+type LegacyCloudNode struct {
+	Price float64 `json:"price"`
+	Size  string  `json:"size"`
+}
+
+// Node is a struct for records created by Arvados Dispatch Cloud (Arvados >= 2.0.0)
+// Example:
+// {
+//    "Name": "Standard_D1_v2",
+//    "ProviderType": "Standard_D1_v2",
+//    "VCPUs": 1,
+//    "RAM": 3584000000,
+//    "Scratch": 50000000000,
+//    "IncludedScratch": 50000000000,
+//    "AddedScratch": 0,
+//    "Price": 0.057,
+//    "Preemptible": false
+//}
+type Node struct {
+	VCPUs        int64
+	Scratch      int64
+	RAM          int64
+	Price        float64
+	Name         string
+	ProviderType string
+	Preemptible  bool
+}
+
+type report struct {
+	Type string
+	Msg  string
+}
+
+type arrayFlags []string
+
+func (i *arrayFlags) String() string {
+	return ""
+}
+
+func (i *arrayFlags) Set(value string) error {
+	*i = append(*i, value)
+	return nil
+}
+
+func logError(m []string) {
+	log.Print(string(marshal(report{"Error", strings.Join(m, " ")})))
+}
+
+func marshal(message interface{}) (encoded []byte) {
+	encoded, err := json.Marshal(message)
+	if err != nil {
+		// do not call logError here because that would create an infinite loop
+		fmt.Fprintln(os.Stderr, "{\"Error\": \"Unable to marshal message into json:", message, "\"}")
+		return nil
+	}
+	return
+}
+
+func parseFlags() (uuids arrayFlags) {
+
+	flags := flag.NewFlagSet("cost-analyzer", flag.ExitOnError)
+	flags.Var(&uuids, "uuid", "Toplevel project or container request uuid. May be specified more than once.")
+
+	flags.Usage = func() { usage(flags) }
+
+	// Parse args; omit the first arg which is the command name
+	err := flags.Parse(os.Args[1:])
+	if err != nil {
+		logError([]string{"Unable to parse command line arguments:", err.Error()})
+		os.Exit(1)
+	}
+
+	if len(uuids) == 0 {
+		usage(flags)
+		os.Exit(1)
+	}
+
+	return
+}
+
+func ensureDirectory(dir string) {
+	statData, err := os.Stat(dir)
+	if os.IsNotExist(err) {
+		err = os.MkdirAll(dir, 0700)
+		if err != nil {
+			logError([]string{"Error creating directory", dir, ":", err.Error()})
+			os.Exit(1)
+		}
+	} else {
+		if !statData.IsDir() {
+			logError([]string{"The path", dir, "is not a directory"})
+			os.Exit(1)
+		}
+	}
+}
+
+func addContainerLine(node interface{}, cr Dict, container Dict) (csv string, cost float64) {
+	csv = cr["uuid"].(string) + ","
+	csv += cr["name"].(string) + ","
+	csv += container["uuid"].(string) + ","
+	csv += container["state"].(string) + ","
+	if container["started_at"] != nil {
+		csv += container["started_at"].(string) + ","
+	} else {
+		csv += ","
+	}
+
+	var delta time.Duration
+	if container["finished_at"] != nil {
+		csv += container["finished_at"].(string) + ","
+		finishedTimestamp, err := time.Parse("2006-01-02T15:04:05.000000000Z", container["finished_at"].(string))
+		if err != nil {
+			fmt.Println(err)
+		}
+		startedTimestamp, err := time.Parse("2006-01-02T15:04:05.000000000Z", container["started_at"].(string))
+		if err != nil {
+			fmt.Println(err)
+		}
+		delta = finishedTimestamp.Sub(startedTimestamp)
+		csv += strconv.FormatFloat(delta.Seconds(), 'f', 0, 64) + ","
+	} else {
+		csv += ",,"
+	}
+	var price float64
+	var size string
+	switch n := node.(type) {
+	case Node:
+		price = n.Price
+		size = n.ProviderType
+	case LegacyNodeInfo:
+		price = n.CloudNode.Price
+		size = n.CloudNode.Size
+	default:
+		log.Printf("WARNING: unknown node type found!")
+	}
+	cost = delta.Seconds() / 3600 * price
+	csv += size + "," + strconv.FormatFloat(price, 'f', 8, 64) + "," + strconv.FormatFloat(cost, 'f', 8, 64) + "\n"
+	return
+}
+
+func loadCachedObject(file string, uuid string) (reload bool, object Dict) {
+	reload = true
+	// See if we have a cached copy of this object
+	if _, err := os.Stat(file); err == nil {
+		data, err := ioutil.ReadFile(file)
+		if err != nil {
+			log.Printf("error reading %q: %s", file, err)
+			return
+		}
+		err = json.Unmarshal(data, &object)
+		if err != nil {
+			log.Printf("failed to unmarshal json: %s: %s", data, err)
+			return
+		}
+
+		// See if it is in a final state, if that makes sense
+		// Projects (j7d0g) do not have state so they should always be reloaded
+		if !strings.Contains(uuid, "-j7d0g-") {
+			if object["state"].(string) == "Complete" || object["state"].(string) == "Failed" {
+				reload = false
+				return
+			}
+		}
+	}
+	return
+}
+
+// Load an Arvados object.
+func loadObject(arv *arvadosclient.ArvadosClient, path string, uuid string) (object Dict) {
+
+	ensureDirectory(path)
+
+	file := path + "/" + uuid + ".json"
+
+	var reload bool
+	reload, object = loadCachedObject(file, uuid)
+
+	if reload {
+		var err error
+		if strings.Contains(uuid, "-d1hrv-") {
+			err = arv.Get("pipeline_instances", uuid, nil, &object)
+		} else if strings.Contains(uuid, "-j7d0g-") {
+			err = arv.Get("groups", uuid, nil, &object)
+		} else if strings.Contains(uuid, "-xvhdp-") {
+			err = arv.Get("container_requests", uuid, nil, &object)
+		} else if strings.Contains(uuid, "-dz642-") {
+			err = arv.Get("containers", uuid, nil, &object)
+		} else {
+			err = arv.Get("jobs", uuid, nil, &object)
+		}
+		if err != nil {
+			logError([]string{fmt.Sprintf("error loading object with UUID %q: %s", uuid, err)})
+			os.Exit(1)
+		}
+		encoded, err := json.MarshalIndent(object, "", " ")
+		if err != nil {
+			logError([]string{fmt.Sprintf("error marshaling object with UUID %q: %s", uuid, err)})
+			os.Exit(1)
+		}
+		err = ioutil.WriteFile(file, encoded, 0644)
+		if err != nil {
+			logError([]string{fmt.Sprintf("error writing file %s: %s", file, err)})
+			os.Exit(1)
+		}
+	}
+	return
+}
+
+func getNode(arv *arvadosclient.ArvadosClient, arv2 *arvados.Client, kc *keepclient.KeepClient, itemMap Dict) (node interface{}, err error) {
+	if _, ok := itemMap["log_uuid"]; ok {
+		if itemMap["log_uuid"] == nil {
+			err = errors.New("No log collection")
+			return
+		}
+
+		var collection arvados.Collection
+		err = arv.Get("collections", itemMap["log_uuid"].(string), nil, &collection)
+		if err != nil {
+			log.Printf("error getting collection: %s\n", err)
+			return
+		}
+
+		var fs arvados.CollectionFileSystem
+		fs, err = collection.FileSystem(arv2, kc)
+		if err != nil {
+			log.Printf("error opening collection as filesystem: %s\n", err)
+			return
+		}
+		var f http.File
+		f, err = fs.Open("node.json")
+		if err != nil {
+			log.Printf("error opening file in collection: %s\n", err)
+			return
+		}
+
+		var nodeDict Dict
+		// TODO: checkout io (ioutil?) readall function
+		buf := new(bytes.Buffer)
+		_, err = buf.ReadFrom(f)
+		if err != nil {
+			log.Printf("error reading %q: %s\n", f, err)
+			return
+		}
+		contents := buf.String()
+		f.Close()
+
+		err = json.Unmarshal([]byte(contents), &nodeDict)
+		if err != nil {
+			log.Printf("error unmarshalling: %s\n", err)
+			return
+		}
+		if val, ok := nodeDict["properties"]; ok {
+			var encoded []byte
+			encoded, err = json.MarshalIndent(val, "", " ")
+			if err != nil {
+				log.Printf("error marshalling: %s\n", err)
+				return
+			}
+			// node is type LegacyNodeInfo
+			var newNode LegacyNodeInfo
+			err = json.Unmarshal(encoded, &newNode)
+			if err != nil {
+				log.Printf("error unmarshalling: %s\n", err)
+				return
+			}
+			node = newNode
+		} else {
+			// node is type Node
+			var newNode Node
+			err = json.Unmarshal([]byte(contents), &newNode)
+			if err != nil {
+				log.Printf("error unmarshalling: %s\n", err)
+				return
+			}
+			node = newNode
+		}
+	}
+	return
+}
+
+func handleProject(uuid string, arv *arvadosclient.ArvadosClient, arv2 *arvados.Client, kc *keepclient.KeepClient) (cost map[string]float64) {
+
+	cost = make(map[string]float64)
+
+	project := loadObject(arv, "results"+"/"+uuid, uuid)
+
+	// arv -f uuid container_request list --filters '[["owner_uuid","=","<someuuid>"],["requesting_container_uuid","=",null]]'
+
+	// Now find all container requests that have the container we found above as requesting_container_uuid
+	var childCrs map[string]interface{}
+	filterset := []arvados.Filter{
+		{
+			Attr:     "owner_uuid",
+			Operator: "=",
+			Operand:  project["uuid"].(string),
+		},
+		{
+			Attr:     "requesting_container_uuid",
+			Operator: "=",
+			Operand:  nil,
+		},
+	}
+	err := arv.List("container_requests", arvadosclient.Dict{"filters": filterset, "limit": 10000}, &childCrs)
+	if err != nil {
+		log.Fatal("error querying container_requests", err.Error())
+	}
+	if value, ok := childCrs["items"]; ok {
+		log.Println("Collecting top level container requests in project")
+		items := value.([]interface{})
+		for _, item := range items {
+			itemMap := item.(map[string]interface{})
+			for k, v := range generateCrCsv(itemMap["uuid"].(string), arv, arv2, kc) {
+				cost[k] = v
+			}
+		}
+	}
+	return
+}
+
+func generateCrCsv(uuid string, arv *arvadosclient.ArvadosClient, arv2 *arvados.Client, kc *keepclient.KeepClient) (cost map[string]float64) {
+
+	cost = make(map[string]float64)
+
+	csv := "CR UUID,CR name,Container UUID,State,Started At,Finished At,Duration in seconds,Compute node type,Hourly node cost,Total cost\n"
+	var tmpCsv string
+	var tmpTotalCost float64
+	var totalCost float64
+
+	// This is a container request, find the container
+	cr := loadObject(arv, "results"+"/"+uuid, uuid)
+	container := loadObject(arv, "results"+"/"+uuid, cr["container_uuid"].(string))
+
+	topNode, err := getNode(arv, arv2, kc, cr)
+	if err != nil {
+		log.Fatalf("error getting node: %s", err)
+	}
+	tmpCsv, totalCost = addContainerLine(topNode, cr, container)
+	csv += tmpCsv
+	totalCost += tmpTotalCost
+
+	cost[container["uuid"].(string)] = totalCost
+
+	// Now find all container requests that have the container we found above as requesting_container_uuid
+	var childCrs map[string]interface{}
+	filterset := []arvados.Filter{
+		{
+			Attr:     "requesting_container_uuid",
+			Operator: "=",
+			Operand:  container["uuid"].(string),
+		}}
+	err = arv.List("container_requests", arvadosclient.Dict{"filters": filterset, "limit": 10000}, &childCrs)
+	if err != nil {
+		log.Fatal("error querying container_requests", err.Error())
+	}
+	if value, ok := childCrs["items"]; ok {
+		log.Println("Collecting child containers")
+		items := value.([]interface{})
+		for _, item := range items {
+			fmt.Fprintf(os.Stderr, ".")
+			itemMap := item.(map[string]interface{})
+			node, _ := getNode(arv, arv2, kc, itemMap)
+			c2 := loadObject(arv, "results"+"/"+uuid, itemMap["container_uuid"].(string))
+			tmpCsv, tmpTotalCost = addContainerLine(node, itemMap, c2)
+			cost[itemMap["container_uuid"].(string)] = tmpTotalCost
+			csv += tmpCsv
+			totalCost += tmpTotalCost
+		}
+	}
+	fmt.Fprintf(os.Stderr, "\n")
+	log.Println("Done")
+
+	csv += "TOTAL,,,,,,,,," + strconv.FormatFloat(totalCost, 'f', 8, 64) + "\n"
+
+	// Write the resulting CSV file
+	err = ioutil.WriteFile("results"+"/"+uuid+".csv", []byte(csv), 0644)
+	if err != nil {
+		logError([]string{"Error writing file", ":", err.Error()})
+		os.Exit(1)
+	}
+
+	log.Println("Results in results/" + uuid + ".csv")
+	return
+}
+
+func main() {
+
+	uuids := parseFlags()
+
+	ensureDirectory("results")
+
+	// Arvados Client setup
+	arv, err := arvadosclient.MakeArvadosClient()
+	if err != nil {
+		logError([]string{fmt.Sprintf("error creating Arvados object: %s", err)})
+		os.Exit(1)
+	}
+	kc, err := keepclient.MakeKeepClient(arv)
+	if err != nil {
+		logError([]string{fmt.Sprintf("error creating Keep object: %s", err)})
+		os.Exit(1)
+	}
+
+	arv2 := arvados.NewClientFromEnv()
+
+	cost := make(map[string]float64)
+
+	for _, uuid := range uuids {
+		//csv := "CR UUID,CR name,Container UUID,State,Started At,Finished At,Duration in seconds,Compute node type,Hourly node cost,Total cost\n"
+
+		if strings.Contains(uuid, "-d1hrv-") {
+			// This is a pipeline instance, not a job! Find the cwl-runner job.
+			pi := loadObject(arv, "results"+"/"+uuid, uuid)
+			for _, v := range pi["components"].(map[string]interface{}) {
+				x := v.(map[string]interface{})
+				y := x["job"].(map[string]interface{})
+				uuid = y["uuid"].(string)
+			}
+		}
+
+		// for projects:
+		// arv -f uuid container_request list --filters '[["owner_uuid","=","<someuuid>"],["requesting_container_uuid","=",null]]'
+
+		// Is this a project?
+		if strings.Contains(uuid, "-j7d0g-") {
+			for k, v := range handleProject(uuid, arv, arv2, kc) {
+				cost[k] = v
+			}
+		}
+		// Is this a container request?
+		if strings.Contains(uuid, "-xvhdp-") {
+			for k, v := range generateCrCsv(uuid, arv, arv2, kc) {
+				cost[k] = v
+			}
+		}
+	}
+
+	var csv string
+
+	csv = "# Aggregate cost accounting for uuids:\n"
+	for _, uuid := range uuids {
+		csv += "# " + uuid + "\n"
+	}
+
+	var total float64
+	for k, v := range cost {
+		csv += k + "," + strconv.FormatFloat(v, 'f', 8, 64) + "\n"
+		total += v
+	}
+
+	csv += "TOTAL," + strconv.FormatFloat(total, 'f', 8, 64) + "\n"
+
+	// Write the resulting CSV file
+	err = ioutil.WriteFile("results"+"/"+time.Now().Format("2006-01-02-15-04-05")+"-aggregate-costaccounting.csv", []byte(csv), 0644)
+	if err != nil {
+		logError([]string{"Error writing file", ":", err.Error()})
+		os.Exit(1)
+	}
+}
diff --git a/costanalyzer/go.mod b/costanalyzer/go.mod
new file mode 100644
index 0000000..f4a3b97
--- /dev/null
+++ b/costanalyzer/go.mod
@@ -0,0 +1,5 @@
+module git.arvados.org/arvados-dev.git/costanalyzer
+
+go 1.14
+
+require git.arvados.org/arvados.git v0.0.0-20200429210919-3f0914ec893c
diff --git a/costanalyzer/go.sum b/costanalyzer/go.sum
new file mode 100644
index 0000000..59e1836
--- /dev/null
+++ b/costanalyzer/go.sum
@@ -0,0 +1,207 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+git.arvados.org/arvados.git v0.0.0-20200429210919-3f0914ec893c h1:66Z1SXCxeDgaUxV4xrklArwsU0snOyFndUpBmqVHcaY=
+git.arvados.org/arvados.git v0.0.0-20200429210919-3f0914ec893c/go.mod h1:UwG208snh5NCR9ATp+EjYP1D4SQ55LDAb7Ggs0ytuGI=
+github.com/AdRoll/goamz v0.0.0-20170825154802-2731d20f46f4/go.mod h1:bix3XpsJxNavm6XVKAuEFzG+1W3ORxj7hvbIrFr7Sqs=
+github.com/Azure/azure-sdk-for-go v19.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
+github.com/Azure/go-autorest v10.15.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Microsoft/go-winio v0.4.5/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/arvados/cgofuse v1.2.0-arvados1/go.mod h1:79WFV98hrkRHK9XPhh2IGGOwpFSjocsWubgxAs2KhRc=
+github.com/aws/aws-sdk-go v1.25.30/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
+github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
+github.com/coreos/go-systemd v0.0.0-20180108085132-cc4f39464dc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.1.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
+github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
+github.com/docker/distribution v2.6.0-rc.1.0.20180105232752-277ed486c948+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v1.4.2-0.20180109013817-94b8a116fbf1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.3.3-0.20171221200356-d59758554a3d/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.1-0.20180107155708-5bbbb5b2b572/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/imdario/mergo v0.3.8-0.20190415133143-5ef87b449ca7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff/go.mod h1:ddfPX8Z28YMjiqoaJhNBzWHapTHXejnB5cDCUWDwriw=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/karalabe/xgo v0.0.0-20191115072854-c5ccff8648a7/go.mod h1:iYGcTYIPUvEWhFo6aKUuLchs+AV4ssYdyuBbQJZGcBk=
+github.com/kevinburke/ssh_config v0.0.0-20171013211458-802051befeb5/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/marstr/guid v1.1.1-0.20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/msteinert/pam v0.0.0-20190215180659-f29b9f28d6f9/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/image-spec v1.0.1-0.20171125024018-577479e4dc27/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
+github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
+github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
+github.com/satori/go.uuid v1.2.1-0.20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/xanzy/ssh-agent v0.1.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII=
+golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405 h1:829vOVxxusYHC+IqBtkX5mbKtsY9fheQiQn0MZRVLfQ=
+gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/src-d/go-billy.v4 v4.0.1/go.mod h1:ZHSF0JP+7oD97194otDUCD7Ofbk63+xFcfWP5bT6h+Q=
+gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
+gopkg.in/src-d/go-git.v4 v4.0.0/go.mod h1:CzbUWqMn4pvmvndg3gnh5iZFmSsbhyhUWdI0IQ60AQo=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+rsc.io/getopt v0.0.0-20170811000552-20be20937449/go.mod h1:dhCdeqAxkyt5u3/sKRkUXuHaMXUu1Pt13GTQAM2xnig=
diff --git a/costanalyzer/usage.go b/costanalyzer/usage.go
new file mode 100644
index 0000000..c58ac89
--- /dev/null
+++ b/costanalyzer/usage.go
@@ -0,0 +1,20 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package main
+
+import (
+  "flag"
+  "fmt"
+  "os"
+)
+
+func usage(fs *flag.FlagSet) {
+  fmt.Fprintf(os.Stderr, `
+Cost Analyzer analyzes the cost of an Arvados container request and all its children.
+
+Options:
+`)
+  fs.PrintDefaults()
+}

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list