2
0
Эх сурвалжийг харах

Move all regex.MustCompile calls to vars

regexp.MustCompile should be instantiated in a var so the panic happens
at startup. This also has a lovely side effect of improving performance
since the regex is not compiled every time it needs to be used.

In window.go there is a 20x performance improvement with this change.
Daniel Ramich 3 жил өмнө
parent
commit
0f1310458a

+ 18 - 11
pkg/cloud/awsprovider.go

@@ -6,7 +6,6 @@ import (
 	"context"
 	"encoding/csv"
 	"fmt"
-	"github.com/kubecost/opencost/pkg/kubecost"
 	"io"
 	"io/ioutil"
 	"net/http"
@@ -16,6 +15,8 @@ import (
 	"sync"
 	"time"
 
+	"github.com/kubecost/opencost/pkg/kubecost"
+
 	"github.com/kubecost/opencost/pkg/clustercache"
 	"github.com/kubecost/opencost/pkg/env"
 	"github.com/kubecost/opencost/pkg/errors"
@@ -40,14 +41,23 @@ import (
 	v1 "k8s.io/api/core/v1"
 )
 
-const supportedSpotFeedVersion = "1"
-const SpotInfoUpdateType = "spotinfo"
-const AthenaInfoUpdateType = "athenainfo"
-const PreemptibleType = "preemptible"
+const (
+	supportedSpotFeedVersion = "1"
+	SpotInfoUpdateType       = "spotinfo"
+	AthenaInfoUpdateType     = "athenainfo"
+	PreemptibleType          = "preemptible"
 
-const APIPricingSource = "Public API"
-const SpotPricingSource = "Spot Data Feed"
-const ReservedInstancePricingSource = "Savings Plan, Reserved Instance, and Out-Of-Cluster"
+	APIPricingSource              = "Public API"
+	SpotPricingSource             = "Spot Data Feed"
+	ReservedInstancePricingSource = "Savings Plan, Reserved Instance, and Out-Of-Cluster"
+)
+
+var (
+	// It's of the form aws:///us-east-2a/i-0fea4fd46592d050b and we want i-0fea4fd46592d050b, if it exists
+	provIdRx      = regexp.MustCompile("aws:///([^/]+)/([^/]+)")
+	usageTypeRegx = regexp.MustCompile(".*(-|^)(EBS.+)")
+	versionRx     = regexp.MustCompile("^#Version: (\\d+)\\.\\d+$")
+)
 
 func (aws *AWS) PricingSourceStatus() map[string]*PricingSource {
 
@@ -605,7 +615,6 @@ func (k *awsKey) GPUType() string {
 }
 
 func (k *awsKey) ID() string {
-	provIdRx := regexp.MustCompile("aws:///([^/]+)/([^/]+)") // It's of the form aws:///us-east-2a/i-0fea4fd46592d050b and we want i-0fea4fd46592d050b, if it exists
 	for matchNum, group := range provIdRx.FindStringSubmatch(k.ProviderID) {
 		if matchNum == 2 {
 			return group
@@ -932,7 +941,6 @@ func (aws *AWS) DownloadPricingData() error {
 				} else if strings.Contains(product.Attributes.UsageType, "EBS:Volume") {
 					// UsageTypes may be prefixed with a region code - we're removing this when using
 					// volTypes to keep lookups generic
-					usageTypeRegx := regexp.MustCompile(".*(-|^)(EBS.+)")
 					usageTypeMatch := usageTypeRegx.FindStringSubmatch(product.Attributes.UsageType)
 					usageTypeNoRegion := usageTypeMatch[len(usageTypeMatch)-1]
 					key := locationToRegion[product.Attributes.Location] + "," + usageTypeNoRegion
@@ -1992,7 +2000,6 @@ func (aws *AWS) parseSpotData(bucket string, prefix string, projectID string, re
 		keys = append(keys, obj.Key)
 	}
 
-	versionRx := regexp.MustCompile("^#Version: (\\d+)\\.\\d+$")
 	header, err := csvutil.Header(spotInfo{}, "csv")
 	if err != nil {
 		return nil, err

+ 5 - 4
pkg/cloud/azureprovider.go

@@ -66,6 +66,10 @@ var (
 	mtStandardL, _ = regexp.Compile(`^Standard_L\d+[_v\d]*[_Promo]*$`)
 	mtStandardM, _ = regexp.Compile(`^Standard_M\d+[m|t|l]*s[_v\d]*[_Promo]*$`)
 	mtStandardN, _ = regexp.Compile(`^Standard_N[C|D|V]\d+r?[_v\d]*[_Promo]*$`)
+
+	// azure:///subscriptions/0badafdf-1234-abcd-wxyz-123456789/...
+	//  => 0badafdf-1234-abcd-wxyz-123456789
+	azureSubRegex = regexp.MustCompile("azure:///subscriptions/([^/]*)/*")
 )
 
 // List obtained by installing the Azure CLI tool "az", described here:
@@ -1297,10 +1301,7 @@ func (az *Azure) Regions() []string {
 }
 
 func parseAzureSubscriptionID(id string) string {
-	// azure:///subscriptions/0badafdf-1234-abcd-wxyz-123456789/...
-	//  => 0badafdf-1234-abcd-wxyz-123456789
-	rx := regexp.MustCompile("azure:///subscriptions/([^/]*)/*")
-	match := rx.FindStringSubmatch(id)
+	match := azureSubRegex.FindStringSubmatch(id)
 	if len(match) >= 2 {
 		return match[1]
 	}

+ 0 - 2
pkg/cloud/csvprovider.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"regexp"
 	"strconv"
 	"strings"
 	"sync"
@@ -240,7 +239,6 @@ func NodeValueFromMapField(m string, n *v1.Node, useRegion bool) string {
 		}
 	}
 	if len(mf) == 2 && mf[0] == "spec" && mf[1] == "providerID" {
-		provIdRx := regexp.MustCompile("aws:///([^/]+)/([^/]+)") // It's of the form aws:///us-east-2a/i-0fea4fd46592d050b and we want i-0fea4fd46592d050b, if it exists
 		for matchNum, group := range provIdRx.FindStringSubmatch(n.Spec.ProviderID) {
 			if matchNum == 2 {
 				return toReturn + group

+ 9 - 4
pkg/cloud/gcpprovider.go

@@ -69,6 +69,13 @@ var gcpRegions = []string{
 	"us-west4",
 }
 
+var (
+	nvidiaGPURegex = regexp.MustCompile("(Nvidia Tesla [^ ]+) ")
+	// gce://guestbook-12345/...
+	//  => guestbook-12345
+	gceRegex = regexp.MustCompile("gce://([^/]*)/*")
+)
+
 type userAgentTransport struct {
 	userAgent string
 	base      http.RoundTripper
@@ -597,8 +604,7 @@ func (gcp *GCP) parsePage(r io.Reader, inputKeys map[string]Key, pvKeys map[stri
 					}
 				*/
 				var gpuType string
-				provIdRx := regexp.MustCompile("(Nvidia Tesla [^ ]+) ")
-				for matchnum, group := range provIdRx.FindStringSubmatch(product.Description) {
+				for matchnum, group := range nvidiaGPURegex.FindStringSubmatch(product.Description) {
 					if matchnum == 1 {
 						gpuType = strings.ToLower(strings.Join(strings.Split(group, " "), "-"))
 						log.Debug("GPU type found: " + gpuType)
@@ -1391,8 +1397,7 @@ func sustainedUseDiscount(class string, defaultDiscount float64, isPreemptible b
 func parseGCPProjectID(id string) string {
 	// gce://guestbook-12345/...
 	//  => guestbook-12345
-	rx := regexp.MustCompile("gce://([^/]*)/*")
-	match := rx.FindStringSubmatch(id)
+	match := gceRegex.FindStringSubmatch(id)
 	if len(match) >= 2 {
 		return match[1]
 	}

+ 18 - 13
pkg/cloud/provider.go

@@ -4,7 +4,6 @@ import (
 	"database/sql"
 	"errors"
 	"fmt"
-	"github.com/kubecost/opencost/pkg/kubecost"
 	"io"
 	"regexp"
 	"strconv"
@@ -12,6 +11,8 @@ import (
 	"sync"
 	"time"
 
+	"github.com/kubecost/opencost/pkg/kubecost"
+
 	"github.com/kubecost/opencost/pkg/util"
 
 	"cloud.google.com/go/compute/metadata"
@@ -607,20 +608,27 @@ func GetOrCreateClusterMeta(cluster_id, cluster_name string) (string, string, er
 	return id, name, nil
 }
 
+var (
+	// It's of the form aws:///us-east-2a/i-0fea4fd46592d050b and we want i-0fea4fd46592d050b, if it exists
+	providerAWSRegex = regexp.MustCompile("aws://[^/]*/[^/]*/([^/]+)")
+	// gce://guestbook-227502/us-central1-a/gke-niko-n1-standard-2-wljla-8df8e58a-hfy7
+	//  => gke-niko-n1-standard-2-wljla-8df8e58a-hfy7
+	providerGCERegex = regexp.MustCompile("gce://[^/]*/[^/]*/([^/]+)")
+	// Capture "vol-0fc54c5e83b8d2b76" from "aws://us-east-2a/vol-0fc54c5e83b8d2b76"
+	persistentVolumeAWSRegex = regexp.MustCompile("aws:/[^/]*/[^/]*/([^/]+)")
+	// Capture "ad9d88195b52a47c89b5055120f28c58" from "ad9d88195b52a47c89b5055120f28c58-1037804914.us-east-2.elb.amazonaws.com"
+	loadBalancerAWSRegex = regexp.MustCompile("^([^-]+)-.+amazonaws\\.com$")
+)
+
 // ParseID attempts to parse a ProviderId from a string based on formats from the various providers and
 // returns the string as is if it cannot find a match
 func ParseID(id string) string {
-	// It's of the form aws:///us-east-2a/i-0fea4fd46592d050b and we want i-0fea4fd46592d050b, if it exists
-	rx := regexp.MustCompile("aws://[^/]*/[^/]*/([^/]+)")
-	match := rx.FindStringSubmatch(id)
+	match := providerAWSRegex.FindStringSubmatch(id)
 	if len(match) >= 2 {
 		return match[1]
 	}
 
-	// gce://guestbook-227502/us-central1-a/gke-niko-n1-standard-2-wljla-8df8e58a-hfy7
-	//  => gke-niko-n1-standard-2-wljla-8df8e58a-hfy7
-	rx = regexp.MustCompile("gce://[^/]*/[^/]*/([^/]+)")
-	match = rx.FindStringSubmatch(id)
+	match = providerGCERegex.FindStringSubmatch(id)
 	if len(match) >= 2 {
 		return match[1]
 	}
@@ -632,9 +640,7 @@ func ParseID(id string) string {
 // ParsePVID attempts to parse a PV ProviderId from a string based on formats from the various providers and
 // returns the string as is if it cannot find a match
 func ParsePVID(id string) string {
-	// Capture "vol-0fc54c5e83b8d2b76" from "aws://us-east-2a/vol-0fc54c5e83b8d2b76"
-	rx := regexp.MustCompile("aws:/[^/]*/[^/]*/([^/]+)")
-	match := rx.FindStringSubmatch(id)
+	match := persistentVolumeAWSRegex.FindStringSubmatch(id)
 	if len(match) >= 2 {
 		return match[1]
 	}
@@ -646,8 +652,7 @@ func ParsePVID(id string) string {
 // ParseLBID attempts to parse a LB ProviderId from a string based on formats from the various providers and
 // returns the string as is if it cannot find a match
 func ParseLBID(id string) string {
-	rx := regexp.MustCompile("^([^-]+)-.+amazonaws\\.com$") // Capture "ad9d88195b52a47c89b5055120f28c58" from "ad9d88195b52a47c89b5055120f28c58-1037804914.us-east-2.elb.amazonaws.com"
-	match := rx.FindStringSubmatch(id)
+	match := loadBalancerAWSRegex.FindStringSubmatch(id)
 	if len(match) >= 2 {
 		return match[1]
 	}

+ 16 - 12
pkg/costmodel/aggregation.go

@@ -1881,6 +1881,20 @@ func (a *Accesses) warmAggregateCostModelCache() {
 	}
 }
 
+var (
+	// Convert UTC-RFC3339 pairs to configured UTC offset
+	// e.g. with UTC offset of -0600, 2020-07-01T00:00:00Z becomes
+	// 2020-07-01T06:00:00Z == 2020-07-01T00:00:00-0600
+	// TODO niko/etl fix the frontend because this is confusing if you're
+	// actually asking for UTC time (...Z) and we swap that "Z" out for the
+	// configured UTC offset without asking
+	rfc3339      = `\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ`
+	rfc3339Regex = regexp.MustCompile(fmt.Sprintf(`(%s),(%s)`, rfc3339, rfc3339))
+
+	durRegex     = regexp.MustCompile(`^(\d+)(m|h|d|s)$`)
+	percentRegex = regexp.MustCompile(`(\d+\.*\d*)%`)
+)
+
 // AggregateCostModelHandler handles requests to the aggregated cost model API. See
 // ComputeAggregateCostModel for details.
 func (a *Accesses) AggregateCostModelHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
@@ -1888,15 +1902,7 @@ func (a *Accesses) AggregateCostModelHandler(w http.ResponseWriter, r *http.Requ
 
 	windowStr := r.URL.Query().Get("window")
 
-	// Convert UTC-RFC3339 pairs to configured UTC offset
-	// e.g. with UTC offset of -0600, 2020-07-01T00:00:00Z becomes
-	// 2020-07-01T06:00:00Z == 2020-07-01T00:00:00-0600
-	// TODO niko/etl fix the frontend because this is confusing if you're
-	// actually asking for UTC time (...Z) and we swap that "Z" out for the
-	// configured UTC offset without asking
-	rfc3339 := `\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ`
-	regex := regexp.MustCompile(fmt.Sprintf(`(%s),(%s)`, rfc3339, rfc3339))
-	match := regex.FindStringSubmatch(windowStr)
+	match := rfc3339Regex.FindStringSubmatch(windowStr)
 	if match != nil {
 		start, _ := time.Parse(time.RFC3339, match[1])
 		start = start.Add(-env.GetParsedUTCOffset()).In(time.UTC)
@@ -1912,7 +1918,6 @@ func (a *Accesses) AggregateCostModelHandler(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	durRegex := regexp.MustCompile(`^(\d+)(m|h|d|s)$`)
 	isDurationStr := durRegex.MatchString(windowStr)
 
 	// legacy offset option should override window offset
@@ -2076,8 +2081,7 @@ func (a *Accesses) AggregateCostModelHandler(w http.ResponseWriter, r *http.Requ
 				// after the pipeline builds
 				msg := "Data will be available after ETL is built"
 
-				rex := regexp.MustCompile(`(\d+\.*\d*)%`)
-				match := rex.FindStringSubmatch(boundaryErr.Message)
+				match := percentRegex.FindStringSubmatch(boundaryErr.Message)
 				if len(match) > 1 {
 					completionPct, err := strconv.ParseFloat(match[1], 64)
 					if err == nil {

+ 3 - 2
pkg/env/costmodelenv.go

@@ -88,6 +88,8 @@ const (
 	ETLReadOnlyMode = "ETL_READ_ONLY"
 )
 
+var offsetRegex = regexp.MustCompile(`^(\+|-)(\d\d):(\d\d)$`)
+
 func IsETLReadOnlyMode() bool {
 	return GetBool(ETLReadOnlyMode, false)
 }
@@ -400,8 +402,7 @@ func GetParsedUTCOffset() time.Duration {
 	offset := time.Duration(0)
 
 	if offsetStr := GetUTCOffset(); offsetStr != "" {
-		regex := regexp.MustCompile(`^(\+|-)(\d\d):(\d\d)$`)
-		match := regex.FindStringSubmatch(offsetStr)
+		match := offsetRegex.FindStringSubmatch(offsetStr)
 		if match == nil {
 			log.Warnf("Illegal UTC offset: %s", offsetStr)
 			return offset

+ 14 - 11
pkg/kubecost/window.go

@@ -20,6 +20,15 @@ const (
 	hoursPerDay    = 24
 )
 
+var (
+	durationRegex       = regexp.MustCompile(`^(\d+)(m|h|d)$`)
+	durationOffsetRegex = regexp.MustCompile(`^(\d+)(m|h|d) offset (\d+)(m|h|d)$`)
+	offesetRegex        = regexp.MustCompile(`^(\+|-)(\d\d):(\d\d)$`)
+	rfc3339             = `\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ`
+	rfcRegex            = regexp.MustCompile(fmt.Sprintf(`(%s),(%s)`, rfc3339, rfc3339))
+	timestampPairRegex  = regexp.MustCompile(`^(\d+)[,|-](\d+)$`)
+)
+
 // RoundBack rounds the given time back to a multiple of the given resolution
 // in the given time's timezone.
 // e.g. 2020-01-01T12:37:48-0700, 24h = 2020-01-01T00:00:00-0700
@@ -82,8 +91,7 @@ func ParseWindowWithOffsetString(window string, offset string) (Window, error) {
 		return ParseWindowUTC(window)
 	}
 
-	regex := regexp.MustCompile(`^(\+|-)(\d\d):(\d\d)$`)
-	match := regex.FindStringSubmatch(offset)
+	match := offesetRegex.FindStringSubmatch(offset)
 	if match == nil {
 		return Window{}, fmt.Errorf("illegal UTC offset: '%s'; should be of form '-07:00'", offset)
 	}
@@ -215,8 +223,7 @@ func parseWindow(window string, now time.Time) (Window, error) {
 	}
 
 	// Match duration strings; e.g. "45m", "24h", "7d"
-	regex := regexp.MustCompile(`^(\d+)(m|h|d)$`)
-	match := regex.FindStringSubmatch(window)
+	match := durationRegex.FindStringSubmatch(window)
 	if match != nil {
 		dur := time.Minute
 		if match[2] == "h" {
@@ -235,8 +242,7 @@ func parseWindow(window string, now time.Time) (Window, error) {
 	}
 
 	// Match duration strings with offset; e.g. "45m offset 15m", etc.
-	regex = regexp.MustCompile(`^(\d+)(m|h|d) offset (\d+)(m|h|d)$`)
-	match = regex.FindStringSubmatch(window)
+	match = durationOffsetRegex.FindStringSubmatch(window)
 	if match != nil {
 		end := now
 
@@ -268,8 +274,7 @@ func parseWindow(window string, now time.Time) (Window, error) {
 	}
 
 	// Match timestamp pairs, e.g. "1586822400,1586908800" or "1586822400-1586908800"
-	regex = regexp.MustCompile(`^(\d+)[,|-](\d+)$`)
-	match = regex.FindStringSubmatch(window)
+	match = timestampPairRegex.FindStringSubmatch(window)
 	if match != nil {
 		s, _ := strconv.ParseInt(match[1], 10, 64)
 		e, _ := strconv.ParseInt(match[2], 10, 64)
@@ -279,9 +284,7 @@ func parseWindow(window string, now time.Time) (Window, error) {
 	}
 
 	// Match RFC3339 pairs, e.g. "2020-04-01T00:00:00Z,2020-04-03T00:00:00Z"
-	rfc3339 := `\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ`
-	regex = regexp.MustCompile(fmt.Sprintf(`(%s),(%s)`, rfc3339, rfc3339))
-	match = regex.FindStringSubmatch(window)
+	match = rfcRegex.FindStringSubmatch(window)
 	if match != nil {
 		start, _ := time.Parse(time.RFC3339, match[1])
 		end, _ := time.Parse(time.RFC3339, match[2])

+ 9 - 0
pkg/kubecost/window_test.go

@@ -324,6 +324,15 @@ func TestParseWindowUTC(t *testing.T) {
 	}
 }
 
+func BenchmarkParseWindowUTC(b *testing.B) {
+	for n := 0; n < b.N; n++ {
+		_, err := ParseWindowUTC("2020-04-08T00:00:00Z,2020-04-12T00:00:00Z")
+		if err != nil {
+			b.Fatalf("error running benchmark: %s", err.Error())
+		}
+	}
+}
+
 func TestParseWindowWithOffsetString(t *testing.T) {
 	// ParseWindowWithOffsetString should equal ParseWindowUTC when location == "UTC"
 	// for all window string formats