Skip to content
This repository has been archived by the owner on Oct 14, 2024. It is now read-only.

Commit

Permalink
extrapolate estimated scan time from scan size (#669)
Browse files Browse the repository at this point in the history
  • Loading branch information
fishkerez authored Sep 13, 2023
1 parent 23e7064 commit 5b05342
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 42 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ require (
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gonum.org/v1/gonum v0.7.0 // indirect
gonum.org/v1/gonum v0.14.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2656,9 +2656,9 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.7.0 h1:Hdks0L0hgznZLG9nzXb8vZ0rRvqNvAcgAp84y7Mwkgw=
gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=
gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
Expand Down
51 changes: 22 additions & 29 deletions pkg/orchestrator/provider/aws/scanestimation/estimate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@ import (
"context"
"errors"
"fmt"
"math"
"time"

"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/pricing"
kubeclarityUtils "github.com/openclarity/kubeclarity/shared/pkg/utils"

"github.com/openclarity/vmclarity/api/models"
"github.com/openclarity/vmclarity/pkg/orchestrator/provider/common"
familiestypes "github.com/openclarity/vmclarity/pkg/shared/families/types"
"github.com/openclarity/vmclarity/pkg/shared/utils"
)
Expand Down Expand Up @@ -78,17 +77,22 @@ const (
DataTransfer recipeResource = "DataTransfer"
)

// Static times from lab tests of family scan duration in seconds per GB.
// scanSizesGB represents the memory sizes on the machines that the tests were taken on.
var scanSizesGB = []float64{0.01, 2.5, 8.1}

// FamilyScanDurationsMap Calculate the logarithmic fit of each family base on static measurements of family scan duration in seconds per scanSizesGB value.
// The tests were made on a t2.large instance with a gp2 volume.
// The times correspond to the scan size values in scanSizesGB.
// TODO add infoFinder family stats.
// nolint:gomnd
var familyScanDurationPerGBMap = map[familiestypes.FamilyType]time.Duration{
familiestypes.SBOM: time.Duration(4.5 * float64(time.Second)),
familiestypes.Vulnerabilities: 1 * time.Second, // TODO check time with no sbom scan
familiestypes.Secrets: 300 * time.Second,
familiestypes.Exploits: 0,
familiestypes.Rootkits: 0,
familiestypes.Misconfiguration: 1 * time.Second,
familiestypes.Malware: 360 * time.Second,
var FamilyScanDurationsMap = map[familiestypes.FamilyType]*common.LogarithmicFormula{
familiestypes.SBOM: common.MustLogarithmicFit(scanSizesGB, []float64{0.01, 11, 37}),
familiestypes.Vulnerabilities: common.MustLogarithmicFit(scanSizesGB, []float64{0.01, 1, 11}), // TODO check time with no sbom scan
familiestypes.Secrets: common.MustLogarithmicFit(scanSizesGB, []float64{0.01, 720, 1320}),
familiestypes.Exploits: common.MustLogarithmicFit(scanSizesGB, []float64{0, 0, 0}),
familiestypes.Rootkits: common.MustLogarithmicFit(scanSizesGB, []float64{0, 0, 0}),
familiestypes.Misconfiguration: common.MustLogarithmicFit(scanSizesGB, []float64{0.01, 4, 5}),
familiestypes.Malware: common.MustLogarithmicFit(scanSizesGB, []float64{0.01, 840, 2460}),
}

// Reserved Instances are not physical instances, but rather a billing discount that is applied to the running On-Demand Instances in your account.
Expand Down Expand Up @@ -304,17 +308,6 @@ func findMatchingStatsForInputTypeRootFS(stats *[]models.AssetScanInputScanStats
return models.AssetScanInputScanStats{}, false
}

const constantOfProportionality = 2.5

func calculateStaticScanDuration(familyType familiestypes.FamilyType, scanSizeGB float64) int64 {
if scanSizeGB < 1 {
// The log of a value less than 1 is negative.
return int64(familyScanDurationPerGBMap[familyType].Seconds() * scanSizeGB)
}

return int64(familyScanDurationPerGBMap[familiestypes.SBOM].Seconds() * constantOfProportionality * math.Log(scanSizeGB))
}

// nolint:cyclop
func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFamiliesConfig, scanSizeMB int64) int64 {
var totalScanDuration int64
Expand All @@ -327,7 +320,7 @@ func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFam
totalScanDuration += scanDuration
} else {
// if we didn't find the duration from the stats, take it from our static scan duration map.
totalScanDuration += calculateStaticScanDuration(familiestypes.SBOM, scanSizeGB)
totalScanDuration += int64(FamilyScanDurationsMap[familiestypes.SBOM].Evaluate(scanSizeGB))
}
}

Expand All @@ -336,7 +329,7 @@ func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFam
if scanDuration != 0 {
totalScanDuration += scanDuration
} else {
totalScanDuration += calculateStaticScanDuration(familiestypes.Vulnerabilities, scanSizeGB)
totalScanDuration += int64(FamilyScanDurationsMap[familiestypes.Vulnerabilities].Evaluate(scanSizeGB))
}
}

Expand All @@ -345,7 +338,7 @@ func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFam
if scanDuration != 0 {
totalScanDuration += scanDuration
} else {
totalScanDuration += calculateStaticScanDuration(familiestypes.Secrets, scanSizeGB)
totalScanDuration += int64(FamilyScanDurationsMap[familiestypes.Secrets].Evaluate(scanSizeGB))
}
}

Expand All @@ -354,7 +347,7 @@ func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFam
if scanDuration != 0 {
totalScanDuration += scanDuration
} else {
totalScanDuration += calculateStaticScanDuration(familiestypes.Exploits, scanSizeGB)
totalScanDuration += int64(FamilyScanDurationsMap[familiestypes.Exploits].Evaluate(scanSizeGB))
}
}

Expand All @@ -363,7 +356,7 @@ func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFam
if scanDuration != 0 {
totalScanDuration += scanDuration
} else {
totalScanDuration += calculateStaticScanDuration(familiestypes.Rootkits, scanSizeGB)
totalScanDuration += int64(FamilyScanDurationsMap[familiestypes.Rootkits].Evaluate(scanSizeGB))
}
}

Expand All @@ -372,7 +365,7 @@ func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFam
if scanDuration != 0 {
totalScanDuration += scanDuration
} else {
totalScanDuration += calculateStaticScanDuration(familiestypes.Misconfiguration, scanSizeGB)
totalScanDuration += int64(FamilyScanDurationsMap[familiestypes.Misconfiguration].Evaluate(scanSizeGB))
}
}

Expand All @@ -381,7 +374,7 @@ func getScanDuration(stats models.AssetScanStats, familiesConfig *models.ScanFam
if scanDuration != 0 {
totalScanDuration += scanDuration
} else {
totalScanDuration += calculateStaticScanDuration(familiestypes.Malware, scanSizeGB)
totalScanDuration += int64(FamilyScanDurationsMap[familiestypes.Malware].Evaluate(scanSizeGB))
}
}

Expand Down
18 changes: 8 additions & 10 deletions pkg/orchestrator/provider/aws/scanestimation/estimate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,8 @@ func Test_getScanDuration(t *testing.T) {
},
// 360 seconds Secrets scan from stats
// 50 seconds Sbom scan from stats
// 1 * const * log(2.5) (static time * scan size) for Misconfigurations
// 360 * const * log(2.5) for Malware from static table
// 1 * const * log(2.5) (static time * scan size) for Vulnerabilities
wantDuration: 440,
// extrapolated value for Misconfigurations, Malware and Vulnerabilities from static lab tests.
wantDuration: 1953,
},
}
for _, tt := range tests {
Expand Down Expand Up @@ -306,27 +304,27 @@ func TestScanEstimator_EstimateAssetScan(t *testing.T) {
},
},
want: &models.Estimation{
Cost: utils.PointerTo(float32(0.2819062)),
Cost: utils.PointerTo(float32(0.5883259)),
CostBreakdown: &[]models.CostBreakdownComponent{
{
Cost: float32(0.0010364198),
Cost: float32(0.002162963),
Operation: string(Snapshot + "-us-east-1"),
},
{
Cost: float32(0.27983335),
Cost: float32(0.584),
Operation: string(ScannerInstance),
},
{
Cost: float32(0.0005182099),
Cost: float32(0.0010814815),
Operation: string(VolumeFromSnapshot),
},
{
Cost: float32(0.0005182099),
Cost: float32(0.0010814815),
Operation: string(ScannerRootVolume),
},
},
Size: utils.PointerTo(8),
Duration: utils.PointerTo(479),
Duration: utils.PointerTo(2304),
},
wantErr: false,
},
Expand Down
89 changes: 89 additions & 0 deletions pkg/orchestrator/provider/common/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright © 2023 Cisco Systems, Inc. and its affiliates.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common

import (
"fmt"
"math"

"github.com/sirupsen/logrus"
)

// LogarithmicFormula represents the formula y = a * ln(x) + b.
type LogarithmicFormula struct {
a float64
b float64
}

// Evaluate receive an x value and returns the y value of the formula.
func (lf *LogarithmicFormula) Evaluate(x float64) float64 {
return lf.a*math.Log(x) + lf.b
}

func MustLogarithmicFit(xData, yData []float64) *LogarithmicFormula {
ret, err := LogarithmicFit(xData, yData)
if err != nil {
logrus.Panic(err)
}
return ret
}

func LogarithmicFit(xData, yData []float64) (*LogarithmicFormula, error) {
var err error
var a, b float64

a, b, err = logarithmicFit(xData, yData)
if err != nil {
return nil, fmt.Errorf("failed to calculate a logarithmic fit: %w", err)
}
return &LogarithmicFormula{a: a, b: b}, nil
}

// logarithmicFit performs least squares fitting for the logarithmic model y = a * ln(x) + b.
// It returns the a and b constants of the logarithmic model.
func logarithmicFit(xData, yData []float64) (float64, float64, error) {
n := len(xData)
if n != len(yData) || n == 0 {
return 0, 0, fmt.Errorf("input data is invalid. xData: %v, yData: %v", xData, yData)
}

lnXData := make([]float64, n)

for i := 0; i < n; i++ {
if xData[i] <= 0 {
return 0, 0, fmt.Errorf("input data contains non-positive x values: %v", xData)
}
lnXData[i] = math.Log(xData[i])
}

// Calculate the constants of the logarithmic regression model y = a * ln(x) + b
var sumLnX, sumY, sumLnX2, sumLnXY float64
for i := 0; i < n; i++ {
sumLnX += lnXData[i]
sumY += yData[i]
sumLnX2 += lnXData[i] * lnXData[i]
sumLnXY += lnXData[i] * yData[i]
}

if ((float64(n))*sumLnX2 - sumLnX*sumLnX) == 0 {
return 0, 0, fmt.Errorf("zero denominator in calculations")
}

a := ((float64(n))*sumLnXY - sumLnX*sumY) / ((float64(n))*sumLnX2 - sumLnX*sumLnX)
b := (sumY - a*sumLnX) / float64(n)

return a, b, nil
}

0 comments on commit 5b05342

Please sign in to comment.