ソースを参照

Added a string bank utility which can be leveraged in the buffer readstring. Refactor specific utilities into respecitive _util package.

Matt Bolt 4 年 前
コミット
c3577edfbd

+ 2 - 1
pkg/cloud/awsprovider.go

@@ -25,6 +25,7 @@ import (
 	"github.com/kubecost/cost-model/pkg/log"
 	"github.com/kubecost/cost-model/pkg/log"
 	"github.com/kubecost/cost-model/pkg/util"
 	"github.com/kubecost/cost-model/pkg/util"
 	"github.com/kubecost/cost-model/pkg/util/cloudutil"
 	"github.com/kubecost/cost-model/pkg/util/cloudutil"
+	"github.com/kubecost/cost-model/pkg/util/fileutil"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/kubecost/cost-model/pkg/util/json"
 
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws"
@@ -1317,7 +1318,7 @@ func (aws *AWS) loadAWSAuthSecret(force bool) (*AWSAccessKey, error) {
 	}
 	}
 	loadedAWSSecret = true
 	loadedAWSSecret = true
 
 
-	exists, err := util.FileExists(authSecretPath)
+	exists, err := fileutil.FileExists(authSecretPath)
 	if !exists || err != nil {
 	if !exists || err != nil {
 		return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
 		return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
 	}
 	}

+ 8 - 8
pkg/cloud/azureprovider.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"context"
 	"encoding/csv"
 	"encoding/csv"
 	"fmt"
 	"fmt"
-	"github.com/kubecost/cost-model/pkg/log"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
@@ -15,10 +14,13 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
+	"github.com/kubecost/cost-model/pkg/log"
+
 	"github.com/kubecost/cost-model/pkg/clustercache"
 	"github.com/kubecost/cost-model/pkg/clustercache"
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/kubecost"
 	"github.com/kubecost/cost-model/pkg/kubecost"
 	"github.com/kubecost/cost-model/pkg/util"
 	"github.com/kubecost/cost-model/pkg/util"
+	"github.com/kubecost/cost-model/pkg/util/fileutil"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/kubecost/cost-model/pkg/util/json"
 
 
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2017-09-01/skus"
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2017-09-01/skus"
@@ -164,12 +166,12 @@ func getRetailPrice(region string, skuName string, currencyCode string, spot boo
 	var filterParams []string
 	var filterParams []string
 
 
 	if region != "" {
 	if region != "" {
-		regionParam := fmt.Sprintf("armRegionName eq '%s'",region)
+		regionParam := fmt.Sprintf("armRegionName eq '%s'", region)
 		filterParams = append(filterParams, regionParam)
 		filterParams = append(filterParams, regionParam)
 	}
 	}
 
 
 	if skuName != "" {
 	if skuName != "" {
-		skuNameParam := fmt.Sprintf("armSkuName eq '%s'",skuName)
+		skuNameParam := fmt.Sprintf("armSkuName eq '%s'", skuName)
 		filterParams = append(filterParams, skuNameParam)
 		filterParams = append(filterParams, skuNameParam)
 	}
 	}
 
 
@@ -488,7 +490,7 @@ func (az *Azure) loadAzureAuthSecret(force bool) (*AzureServiceKey, error) {
 	}
 	}
 	loadedAzureSecret = true
 	loadedAzureSecret = true
 
 
-	exists, err := util.FileExists(authSecretPath)
+	exists, err := fileutil.FileExists(authSecretPath)
 	if !exists || err != nil {
 	if !exists || err != nil {
 		return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
 		return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
 	}
 	}
@@ -517,7 +519,7 @@ func (az *Azure) loadAzureStorageConfig(force bool) (*AzureStorageConfig, error)
 	}
 	}
 	loadedAzureStorageConfigSecret = true
 	loadedAzureStorageConfigSecret = true
 
 
-	exists, err := util.FileExists(storageConfigSecretPath)
+	exists, err := fileutil.FileExists(storageConfigSecretPath)
 	if !exists || err != nil {
 	if !exists || err != nil {
 		return nil, fmt.Errorf("Failed to locate azure storage config file: %s", storageConfigSecretPath)
 		return nil, fmt.Errorf("Failed to locate azure storage config file: %s", storageConfigSecretPath)
 	}
 	}
@@ -860,8 +862,7 @@ func (az *Azure) NodePricing(key Key) (*Node, error) {
 		return nil, fmt.Errorf("azure: NodePricing: key is of type %T", key)
 		return nil, fmt.Errorf("azure: NodePricing: key is of type %T", key)
 	}
 	}
 	config, _ := az.GetConfig()
 	config, _ := az.GetConfig()
-	if slv, ok := azKey.Labels[config.SpotLabel];
-	ok && slv == config.SpotLabelValue && config.SpotLabel != "" && config.SpotLabelValue != "" {
+	if slv, ok := azKey.Labels[config.SpotLabel]; ok && slv == config.SpotLabelValue && config.SpotLabel != "" && config.SpotLabelValue != "" {
 		features := strings.Split(azKey.Features(), ",")
 		features := strings.Split(azKey.Features(), ",")
 		region := features[0]
 		region := features[0]
 		instance := features[1]
 		instance := features[1]
@@ -897,7 +898,6 @@ func (az *Azure) NodePricing(key Key) (*Node, error) {
 		}
 		}
 	}
 	}
 
 
-
 	if n, ok := az.Pricing[azKey.Features()]; ok {
 	if n, ok := az.Pricing[azKey.Features()]; ok {
 		klog.V(4).Infof("Returning pricing for node %s: %+v from key %s", azKey, n, azKey.Features())
 		klog.V(4).Infof("Returning pricing for node %s: %+v from key %s", azKey, n, azKey.Features())
 		if azKey.isValidGPUNode() {
 		if azKey.isValidGPUNode() {

+ 3 - 2
pkg/cloud/gcpprovider.go

@@ -17,6 +17,7 @@ import (
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/log"
 	"github.com/kubecost/cost-model/pkg/log"
 	"github.com/kubecost/cost-model/pkg/util"
 	"github.com/kubecost/cost-model/pkg/util"
+	"github.com/kubecost/cost-model/pkg/util/fileutil"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/kubecost/cost-model/pkg/util/timeutil"
 	"github.com/kubecost/cost-model/pkg/util/timeutil"
 
 
@@ -196,13 +197,13 @@ func (*GCP) loadGCPAuthSecret() {
 	path := env.GetConfigPathWithDefault("/models/")
 	path := env.GetConfigPathWithDefault("/models/")
 
 
 	keyPath := path + "key.json"
 	keyPath := path + "key.json"
-	keyExists, _ := util.FileExists(keyPath)
+	keyExists, _ := fileutil.FileExists(keyPath)
 	if keyExists {
 	if keyExists {
 		klog.V(1).Infof("GCP Auth Key already exists, no need to load from secret")
 		klog.V(1).Infof("GCP Auth Key already exists, no need to load from secret")
 		return
 		return
 	}
 	}
 
 
-	exists, err := util.FileExists(authSecretPath)
+	exists, err := fileutil.FileExists(authSecretPath)
 	if !exists || err != nil {
 	if !exists || err != nil {
 		errMessage := "Secret does not exist"
 		errMessage := "Secret does not exist"
 		if err != nil {
 		if err != nil {

+ 2 - 2
pkg/cloud/providerconfig.go

@@ -9,7 +9,7 @@ import (
 	"sync"
 	"sync"
 
 
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/env"
-	"github.com/kubecost/cost-model/pkg/util"
+	"github.com/kubecost/cost-model/pkg/util/fileutil"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/microcosm-cc/bluemonday"
 	"github.com/microcosm-cc/bluemonday"
 
 
@@ -217,7 +217,7 @@ func SetCustomPricingField(obj *CustomPricing, name string, value string) error
 // but the error isn't relevant to the path. This can happen when the current
 // but the error isn't relevant to the path. This can happen when the current
 // user doesn't have permission to access the file.
 // user doesn't have permission to access the file.
 func fileExists(filename string) (bool, error) {
 func fileExists(filename string) (bool, error) {
-	return util.FileExists(filename) // delegate to utility method
+	return fileutil.FileExists(filename) // delegate to utility method
 }
 }
 
 
 // Returns the configuration directory concatenated with a specific config file name
 // Returns the configuration directory concatenated with a specific config file name

+ 3 - 3
pkg/clustermanager/clustermanager.go

@@ -8,7 +8,7 @@ import (
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 
 
-	"github.com/kubecost/cost-model/pkg/util"
+	"github.com/kubecost/cost-model/pkg/util/fileutil"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/kubecost/cost-model/pkg/util/json"
 
 
 	"k8s.io/klog"
 	"k8s.io/klog"
@@ -88,7 +88,7 @@ func NewClusterManager(storage ClusterStorage) *ClusterManager {
 func NewConfiguredClusterManager(storage ClusterStorage, config string) *ClusterManager {
 func NewConfiguredClusterManager(storage ClusterStorage, config string) *ClusterManager {
 	clusterManager := NewClusterManager(storage)
 	clusterManager := NewClusterManager(storage)
 
 
-	exists, err := util.FileExists(config)
+	exists, err := fileutil.FileExists(config)
 	if !exists {
 	if !exists {
 		if err != nil {
 		if err != nil {
 			klog.V(1).Infof("[Error] Failed to load config file: %s. Error: %s", config, err.Error())
 			klog.V(1).Infof("[Error] Failed to load config file: %s. Error: %s", config, err.Error())
@@ -212,7 +212,7 @@ func fileFromSecret(secretName string) string {
 
 
 func fromSecret(secretName string) (string, error) {
 func fromSecret(secretName string) (string, error) {
 	file := fileFromSecret(secretName)
 	file := fileFromSecret(secretName)
-	exists, err := util.FileExists(file)
+	exists, err := fileutil.FileExists(file)
 	if !exists || err != nil {
 	if !exists || err != nil {
 		return "", fmt.Errorf("Failed to locate secret: %s", file)
 		return "", fmt.Errorf("Failed to locate secret: %s", file)
 	}
 	}

+ 1 - 1
pkg/util/blockingqueue.go → pkg/collections/blockingqueue.go

@@ -1,4 +1,4 @@
-package util
+package collections
 
 
 import (
 import (
 	"sync"
 	"sync"

+ 4 - 3
pkg/costmodel/aggregation.go

@@ -2,7 +2,6 @@ package costmodel
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"github.com/kubecost/cost-model/pkg/util/timeutil"
 	"math"
 	"math"
 	"net/http"
 	"net/http"
 	"regexp"
 	"regexp"
@@ -11,6 +10,9 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
+	"github.com/kubecost/cost-model/pkg/util/httputil"
+	"github.com/kubecost/cost-model/pkg/util/timeutil"
+
 	"github.com/julienschmidt/httprouter"
 	"github.com/julienschmidt/httprouter"
 	"github.com/kubecost/cost-model/pkg/cloud"
 	"github.com/kubecost/cost-model/pkg/cloud"
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/env"
@@ -1789,7 +1791,6 @@ func (a *Accesses) warmAggregateCostModelCache() {
 			log.Infof("Error building cache %s: %s", window, aggErr)
 			log.Infof("Error building cache %s: %s", window, aggErr)
 		}
 		}
 
 
-
 		totals, err := a.ComputeClusterCosts(promClient, a.CloudProvider, duration, offset, cacheEfficiencyData)
 		totals, err := a.ComputeClusterCosts(promClient, a.CloudProvider, duration, offset, cacheEfficiencyData)
 		if err != nil {
 		if err != nil {
 			log.Infof("Error building cluster costs cache %s", key)
 			log.Infof("Error building cluster costs cache %s", key)
@@ -2064,7 +2065,7 @@ func (a *Accesses) AggregateCostModelHandler(w http.ResponseWriter, r *http.Requ
 	data, message, err = a.AggAPI.ComputeAggregateCostModel(promClient, window, field, subfields, opts)
 	data, message, err = a.AggAPI.ComputeAggregateCostModel(promClient, window, field, subfields, opts)
 
 
 	// Find any warnings in http request context
 	// Find any warnings in http request context
-	warning, _ := util.GetWarning(r)
+	warning, _ := httputil.GetWarning(r)
 
 
 	if err != nil {
 	if err != nil {
 		if emptyErr, ok := err.(*EmptyDataError); ok {
 		if emptyErr, ok := err.(*EmptyDataError); ok {

+ 1 - 1
pkg/costmodel/allocation.go

@@ -1086,7 +1086,7 @@ func applyLabels(podMap map[podKey]*Pod, namespaceLabels map[namespaceKey]map[st
 			}
 			}
 			// Apply namespace labels first, then pod labels so that pod labels
 			// Apply namespace labels first, then pod labels so that pod labels
 			// overwrite namespace labels.
 			// overwrite namespace labels.
-			nsKey := newNamespaceKey(podKey.Cluster, podKey.Namespace)
+			nsKey := podKey.namespaceKey // newNamespaceKey(podKey.Cluster, podKey.Namespace)
 			if labels, ok := namespaceLabels[nsKey]; ok {
 			if labels, ok := namespaceLabels[nsKey]; ok {
 				for k, v := range labels {
 				for k, v := range labels {
 					allocLabels[k] = v
 					allocLabels[k] = v

+ 7 - 6
pkg/costmodel/key.go

@@ -64,9 +64,8 @@ func resultContainerKey(res *prom.QueryResult, clusterLabel, namespaceLabel, pod
 }
 }
 
 
 type podKey struct {
 type podKey struct {
-	Cluster   string
-	Namespace string
-	Pod       string
+	namespaceKey
+	Pod string
 }
 }
 
 
 func (k podKey) String() string {
 func (k podKey) String() string {
@@ -75,9 +74,11 @@ func (k podKey) String() string {
 
 
 func newPodKey(cluster, namespace, pod string) podKey {
 func newPodKey(cluster, namespace, pod string) podKey {
 	return podKey{
 	return podKey{
-		Cluster:   cluster,
-		Namespace: namespace,
-		Pod:       pod,
+		namespaceKey: namespaceKey{
+			Cluster:   cluster,
+			Namespace: namespace,
+		},
+		Pod: pod,
 	}
 	}
 }
 }
 
 

+ 12 - 9
pkg/prom/prom.go

@@ -9,9 +9,12 @@ import (
 	"os"
 	"os"
 	"time"
 	"time"
 
 
+	"github.com/kubecost/cost-model/pkg/collections"
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/env"
 	"github.com/kubecost/cost-model/pkg/log"
 	"github.com/kubecost/cost-model/pkg/log"
-	"github.com/kubecost/cost-model/pkg/util"
+	"github.com/kubecost/cost-model/pkg/util/atomic"
+	"github.com/kubecost/cost-model/pkg/util/fileutil"
+	"github.com/kubecost/cost-model/pkg/util/httputil"
 
 
 	golog "log"
 	golog "log"
 
 
@@ -63,9 +66,9 @@ type RateLimitedPrometheusClient struct {
 	id         string
 	id         string
 	client     prometheus.Client
 	client     prometheus.Client
 	auth       *ClientAuth
 	auth       *ClientAuth
-	queue      util.BlockingQueue
+	queue      collections.BlockingQueue
 	decorator  QueryParamsDecorator
 	decorator  QueryParamsDecorator
-	outbound   *util.AtomicInt32
+	outbound   *atomic.AtomicInt32
 	fileLogger *golog.Logger
 	fileLogger *golog.Logger
 }
 }
 
 
@@ -84,12 +87,12 @@ func NewRateLimitedClient(id string, config prometheus.Config, maxConcurrency in
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	queue := util.NewBlockingQueue()
-	outbound := util.NewAtomicInt32(0)
+	queue := collections.NewBlockingQueue()
+	outbound := atomic.NewAtomicInt32(0)
 
 
 	var logger *golog.Logger
 	var logger *golog.Logger
 	if queryLogFile != "" {
 	if queryLogFile != "" {
-		exists, err := util.FileExists(queryLogFile)
+		exists, err := fileutil.FileExists(queryLogFile)
 		if exists {
 		if exists {
 			os.Remove(queryLogFile)
 			os.Remove(queryLogFile)
 		}
 		}
@@ -219,10 +222,10 @@ func (rlpc *RateLimitedPrometheusClient) Do(ctx context.Context, req *http.Reque
 
 
 	// request names are used as a debug utility to identify requests in queue
 	// request names are used as a debug utility to identify requests in queue
 	contextName := "<none>"
 	contextName := "<none>"
-	if n, ok := util.GetName(req); ok {
+	if n, ok := httputil.GetName(req); ok {
 		contextName = n
 		contextName = n
 	}
 	}
-	query, _ := util.GetQuery(req)
+	query, _ := httputil.GetQuery(req)
 
 
 	rlpc.queue.Enqueue(&workRequest{
 	rlpc.queue.Enqueue(&workRequest{
 		ctx:         ctx,
 		ctx:         ctx,
@@ -273,7 +276,7 @@ func LogQueryRequest(l *golog.Logger, req *http.Request, queueTime time.Duration
 	if l == nil {
 	if l == nil {
 		return
 		return
 	}
 	}
-	qp := util.NewQueryParams(req.URL.Query())
+	qp := httputil.NewQueryParams(req.URL.Query())
 	query := qp.Get("query", "<Unknown>")
 	query := qp.Get("query", "<Unknown>")
 
 
 	l.Printf("[Queue: %fs, Outbound: %fs][Query: %s]\n", queueTime.Seconds(), sendTime.Seconds(), query)
 	l.Printf("[Queue: %fs, Outbound: %fs][Query: %s]\n", queueTime.Seconds(), sendTime.Seconds(), query)

+ 9 - 9
pkg/prom/query.go

@@ -10,7 +10,7 @@ import (
 
 
 	"github.com/kubecost/cost-model/pkg/errors"
 	"github.com/kubecost/cost-model/pkg/errors"
 	"github.com/kubecost/cost-model/pkg/log"
 	"github.com/kubecost/cost-model/pkg/log"
-	"github.com/kubecost/cost-model/pkg/util"
+	"github.com/kubecost/cost-model/pkg/util/httputil"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	"github.com/kubecost/cost-model/pkg/util/json"
 	prometheus "github.com/prometheus/client_golang/api"
 	prometheus "github.com/prometheus/client_golang/api"
 )
 )
@@ -179,9 +179,9 @@ func (ctx *Context) query(query string) (interface{}, prometheus.Warnings, error
 
 
 	// Set QueryContext name if non empty
 	// Set QueryContext name if non empty
 	if ctx.name != "" {
 	if ctx.name != "" {
-		req = util.SetName(req, ctx.name)
+		req = httputil.SetName(req, ctx.name)
 	}
 	}
-	req = util.SetQuery(req, query)
+	req = httputil.SetQuery(req, query)
 
 
 	// Note that the warnings return value from client.Do() is always nil using this
 	// Note that the warnings return value from client.Do() is always nil using this
 	// version of the prometheus client library. We parse the warnings out of the response
 	// version of the prometheus client library. We parse the warnings out of the response
@@ -198,7 +198,7 @@ func (ctx *Context) query(query string) (interface{}, prometheus.Warnings, error
 	statusCode := resp.StatusCode
 	statusCode := resp.StatusCode
 	statusText := http.StatusText(statusCode)
 	statusText := http.StatusText(statusCode)
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
-		return nil, nil, CommErrorf("%d (%s) URL: '%s', Request Headers: '%s', Headers: '%s', Body: '%s' Query: '%s'", statusCode, statusText, req.URL, req.Header, util.HeaderString(resp.Header), body, query)
+		return nil, nil, CommErrorf("%d (%s) URL: '%s', Request Headers: '%s', Headers: '%s', Body: '%s' Query: '%s'", statusCode, statusText, req.URL, req.Header, httputil.HeaderString(resp.Header), body, query)
 	}
 	}
 
 
 	var toReturn interface{}
 	var toReturn interface{}
@@ -292,9 +292,9 @@ func (ctx *Context) queryRange(query string, start, end time.Time, step time.Dur
 
 
 	// Set QueryContext name if non empty
 	// Set QueryContext name if non empty
 	if ctx.name != "" {
 	if ctx.name != "" {
-		req = util.SetName(req, ctx.name)
+		req = httputil.SetName(req, ctx.name)
 	}
 	}
-	req = util.SetQuery(req, query)
+	req = httputil.SetQuery(req, query)
 
 
 	// Note that the warnings return value from client.Do() is always nil using this
 	// Note that the warnings return value from client.Do() is always nil using this
 	// version of the prometheus client library. We parse the warnings out of the response
 	// version of the prometheus client library. We parse the warnings out of the response
@@ -305,20 +305,20 @@ func (ctx *Context) queryRange(query string, start, end time.Time, step time.Dur
 			return nil, nil, fmt.Errorf("Error: %s, Body: %s Query: %s", err.Error(), body, query)
 			return nil, nil, fmt.Errorf("Error: %s, Body: %s Query: %s", err.Error(), body, query)
 		}
 		}
 
 
-		return nil, nil, fmt.Errorf("%d (%s) Headers: %s Error: %s Body: %s Query: %s", resp.StatusCode, http.StatusText(resp.StatusCode), util.HeaderString(resp.Header), body, err.Error(), query)
+		return nil, nil, fmt.Errorf("%d (%s) Headers: %s Error: %s Body: %s Query: %s", resp.StatusCode, http.StatusText(resp.StatusCode), httputil.HeaderString(resp.Header), body, err.Error(), query)
 	}
 	}
 
 
 	// Unsuccessful Status Code, log body and status
 	// Unsuccessful Status Code, log body and status
 	statusCode := resp.StatusCode
 	statusCode := resp.StatusCode
 	statusText := http.StatusText(statusCode)
 	statusText := http.StatusText(statusCode)
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
-		return nil, nil, CommErrorf("%d (%s) Headers: %s, Body: %s Query: %s", statusCode, statusText, util.HeaderString(resp.Header), body, query)
+		return nil, nil, CommErrorf("%d (%s) Headers: %s, Body: %s Query: %s", statusCode, statusText, httputil.HeaderString(resp.Header), body, query)
 	}
 	}
 
 
 	var toReturn interface{}
 	var toReturn interface{}
 	err = json.Unmarshal(body, &toReturn)
 	err = json.Unmarshal(body, &toReturn)
 	if err != nil {
 	if err != nil {
-		return nil, nil, fmt.Errorf("%d (%s) Headers: %s Error: %s Body: %s Query: %s", statusCode, statusText, util.HeaderString(resp.Header), err.Error(), body, query)
+		return nil, nil, fmt.Errorf("%d (%s) Headers: %s Error: %s Body: %s Query: %s", statusCode, statusText, httputil.HeaderString(resp.Header), err.Error(), body, query)
 	}
 	}
 
 
 	warnings := warningsFrom(toReturn)
 	warnings := warningsFrom(toReturn)

+ 1 - 1
pkg/util/atomic.go → pkg/util/atomic/atomicint.go

@@ -1,4 +1,4 @@
-package util
+package atomic
 
 
 import "sync/atomic"
 import "sync/atomic"
 
 

+ 3 - 1
pkg/util/buffer.go

@@ -8,6 +8,8 @@ import (
 	"math"
 	"math"
 	"reflect"
 	"reflect"
 	"unsafe"
 	"unsafe"
+
+	"github.com/kubecost/cost-model/pkg/util/stringutil"
 )
 )
 
 
 // NonPrimitiveTypeError represents an error where the user provided a non-primitive data type for reading/writing
 // NonPrimitiveTypeError represents an error where the user provided a non-primitive data type for reading/writing
@@ -394,7 +396,7 @@ func bytesToString(b []byte) string {
 	// future optimization.
 	// future optimization.
 	//return *(*string)(unsafe.Pointer(&b))
 	//return *(*string)(unsafe.Pointer(&b))
 
 
-	return string(b)
+	return stringutil.Bank(string(b))
 }
 }
 
 
 // Direct string to byte conversion that doesn't allocate.
 // Direct string to byte conversion that doesn't allocate.

+ 1 - 1
pkg/util/file.go → pkg/util/fileutil/fileutil.go

@@ -1,4 +1,4 @@
-package util
+package fileutil
 
 
 import "os"
 import "os"
 
 

+ 1 - 1
pkg/util/http.go → pkg/util/httputil/httputil.go

@@ -1,4 +1,4 @@
-package util
+package httputil
 
 
 import (
 import (
 	"context"
 	"context"

+ 4 - 6
test/util_test.go → pkg/util/httputil/httputil_test.go

@@ -1,10 +1,8 @@
-package test
+package httputil
 
 
 import (
 import (
 	"net/http"
 	"net/http"
 	"testing"
 	"testing"
-
-	"github.com/kubecost/cost-model/pkg/util"
 )
 )
 
 
 func TestHeaderString(t *testing.T) {
 func TestHeaderString(t *testing.T) {
@@ -14,7 +12,7 @@ func TestHeaderString(t *testing.T) {
 	h.Add("bar", "foo")
 	h.Add("bar", "foo")
 	h.Add("Content-Type", "application/octet-stream")
 	h.Add("Content-Type", "application/octet-stream")
 
 
-	s := util.HeaderString(h)
+	s := HeaderString(h)
 	if len(s) == 0 {
 	if len(s) == 0 {
 		t.Errorf("Header String failed to produce a valid output")
 		t.Errorf("Header String failed to produce a valid output")
 		return
 		return
@@ -26,7 +24,7 @@ func TestHeaderString(t *testing.T) {
 func TestEmptyHeader(t *testing.T) {
 func TestEmptyHeader(t *testing.T) {
 	h := make(http.Header)
 	h := make(http.Header)
 
 
-	s := util.HeaderString(h)
+	s := HeaderString(h)
 	if len(s) == 0 {
 	if len(s) == 0 {
 		t.Errorf("Header String failed to produce a valid output")
 		t.Errorf("Header String failed to produce a valid output")
 		return
 		return
@@ -38,7 +36,7 @@ func TestEmptyHeader(t *testing.T) {
 func TestNilHeader(t *testing.T) {
 func TestNilHeader(t *testing.T) {
 	var h http.Header
 	var h http.Header
 
 
-	s := util.HeaderString(h)
+	s := HeaderString(h)
 	if len(s) == 0 {
 	if len(s) == 0 {
 		t.Errorf("Header String failed to produce a valid output")
 		t.Errorf("Header String failed to produce a valid output")
 		return
 		return

+ 21 - 8
pkg/util/strings.go → pkg/util/stringutil/stringutil.go

@@ -1,19 +1,13 @@
-package util
+package stringutil
 
 
 import (
 import (
 	"fmt"
 	"fmt"
 	"math"
 	"math"
 	"math/rand"
 	"math/rand"
+	"sync"
 	"time"
 	"time"
 )
 )
 
 
-func init() {
-	rand.Seed(time.Now().UnixNano())
-}
-
-var alpha = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
-var alphanumeric = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
-
 const (
 const (
 	_ = 1 << (10 * iota)
 	_ = 1 << (10 * iota)
 	// KiB is bytes per Kibibyte
 	// KiB is bytes per Kibibyte
@@ -26,6 +20,25 @@ const (
 	TiB
 	TiB
 )
 )
 
 
+var alpha = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+var alphanumeric = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+
+// Any strings created at runtime, duplicate or not, is copied, even though by specification,
+// a go string is immutable. This utility allows us to cache runtime strings and retrieve them
+// when we expect heavy duplicates.
+var strings sync.Map
+
+func init() {
+	rand.Seed(time.Now().UnixNano())
+}
+
+// Bank will return a non-copy of a string if it has been used before. Otherwise, it will store
+// the string as the unique instance.
+func Bank(s string) string {
+	ss, _ := strings.LoadOrStore(s, s)
+	return ss.(string)
+}
+
 // RandSeq generates a pseudo-random alphabetic string of the given length
 // RandSeq generates a pseudo-random alphabetic string of the given length
 func RandSeq(n int) string {
 func RandSeq(n int) string {
 	b := make([]rune, n)
 	b := make([]rune, n)