package opencost import ( "fmt" "math" "strings" "sync" "time" "github.com/opencost/opencost/core/pkg/filter" "github.com/opencost/opencost/core/pkg/filter/ast" "github.com/opencost/opencost/core/pkg/filter/matcher" "github.com/opencost/opencost/core/pkg/log" "github.com/opencost/opencost/core/pkg/util/timeutil" ) const ( NetworkInsightsServiceUnknown = "unknownService" NetworkInsightsNamespace = "namespace" NetworkInsightsCluster = "cluster" NetworkInsightsPod = "pod" ) type NetworkInsightProperty string func ConvertNetworkInsightPropertiesToString(nips []NetworkInsightProperty) string { aggString := make([]string, len(nips)) for i, agg := range nips { aggString[i] = string(agg) } return strings.Join(aggString, "/") } // Alias for network traffic direction string type NetworkTrafficDirection string const ( NetworkTrafficDirectionNone NetworkTrafficDirection = "" NetworkTrafficDirectionEgress NetworkTrafficDirection = "Egress" NetworkTrafficDirectionIngress NetworkTrafficDirection = "Ingress" ) // Alias for network traffic type string type NetworkTrafficType string const ( NetworkTrafficTypeNone NetworkTrafficType = "" NetworkTrafficTypeCrossZone NetworkTrafficType = "CrossZone" NetworkTrafficTypeCrossRegion NetworkTrafficType = "CrossRegion" NetworkTrafficTypeInternet NetworkTrafficType = "Internet" ) // Struct to store the filter options applied on network // interaction in networkDetails of network insight type NetworkDetailsOptions struct { ShowZeroCost bool FilterNetworkDetails filter.Filter } // struct to lowest level Ingress and Egress details, interaction // with the endPoint, which is a source in case of Ingress and // destination in case of Egress and also stores Traffic type property, // which describes the traffic as either Internet, Cross Region or Cross Zone type NetworkDetail struct { Cost float64 `json:"cost"` Bytes float64 `json:"bytes"` EndPoint string `json:"endPoint"` TrafficDirection NetworkTrafficDirection `json:"trafficDirection"` TrafficType NetworkTrafficType `json:"trafficType"` } func NewNetworkDetail(cost float64, bytes float64, endPoint string, trafficDirection NetworkTrafficDirection, trafficType NetworkTrafficType) *NetworkDetail { return &NetworkDetail{ Cost: cost, Bytes: bytes, EndPoint: endPoint, TrafficDirection: trafficDirection, TrafficType: trafficType, } } func (nd *NetworkDetail) Clone() *NetworkDetail { return &NetworkDetail{ Cost: nd.Cost, Bytes: nd.Bytes, EndPoint: nd.EndPoint, TrafficDirection: nd.TrafficDirection, TrafficType: nd.TrafficType, } } func (nd *NetworkDetail) Key() string { if nd == nil { return "" } return fmt.Sprintf("%s/%s/%s", nd.EndPoint, nd.TrafficDirection, nd.TrafficType) } func (nd *NetworkDetail) Add(that *NetworkDetail) { if nd == nil { return } if nd.Key() != that.Key() { log.Warnf("adding two NetworkDetail that dont match with key %s: %s", nd.Key(), that.Key()) } nd.Cost += that.Cost nd.Bytes += that.Bytes } func (nd *NetworkDetail) SanitizeNaN() { if math.IsNaN(nd.Cost) { log.DedupedWarningf(5, "NetworkDetail: Unexpected NaN found for Cost: name: %s", nd.Key()) nd.Cost = 0 } if math.IsNaN(nd.Bytes) { log.DedupedWarningf(5, "NetworkDetail: Unexpected NaN found for Bytes: name: %s", nd.Key()) nd.Bytes = 0 } } func (nd *NetworkDetail) IsZeroCost() bool { if nd == nil { log.DedupedWarningf(5, "nd.IsZeroCost called on a nil network detail") return false } return nd.Cost == 0.0 } type NetworkDetailsSet map[string]*NetworkDetail func (nds NetworkDetailsSet) Clone() NetworkDetailsSet { retnids := make(NetworkDetailsSet, 0) for name, nid := range nds { retnids[name] = nid.Clone() } return retnids } func (nds NetworkDetailsSet) Add(nd *NetworkDetail) { key := nd.Key() if _, ok := nds[key]; ok { nds[key].Add(nd) } else { nds[key] = nd.Clone() } } // GetTotalInternetCost Gets the total internet cost in egress details i.e has internet flag true func (nds NetworkDetailsSet) GetTotalInternetCost() float64 { totalCost := 0.0 for _, nd := range nds { if nd.TrafficType == NetworkTrafficTypeInternet { totalCost += nd.Cost } } return totalCost } // Gets the total cross zone cost in egress details i.e has sameZone flag false func (nds NetworkDetailsSet) GetCrossZoneCost() float64 { totalCost := 0.0 for _, nd := range nds { if nd.TrafficType == NetworkTrafficTypeCrossZone { totalCost += nd.Cost } } return totalCost } // Gets the total cross region cost in egress details i.e has sameRegion flag false func (nds NetworkDetailsSet) GetCrossRegionCost() float64 { totalCost := 0.0 for _, nd := range nds { if nd.TrafficType == NetworkTrafficTypeCrossRegion { totalCost += nd.Cost } } return totalCost } func (nds NetworkDetailsSet) Combine(that NetworkDetailsSet) { for _, nd := range that { nds.Add(nd) } } func (nds NetworkDetailsSet) SanitizeNaN() { for _, nd := range nds { nd.SanitizeNaN() } } // filterZeroCost returns a new NetworkDetailsSet with all zero-cost details removed func (nds NetworkDetailsSet) filterZeroCost() NetworkDetailsSet { newNds := make(map[string]*NetworkDetail, 0) for key, nd := range nds { if nd.IsZeroCost() { continue } newNds[key] = nd.Clone() } return newNds } // NetworkInsight struct that stores pod interactions both egress and ingress // Currently only Cluster, namespace and pod will be populated from promsource.go // Rest are placeholders if we need to support single cluster via cost-model // using the same prometheus source.In aggregator we will join allocation of // same time window to get the controller, node, labels, region and zone data. type NetworkInsight struct { Cluster string `json:"cluster"` Namespace string `json:"namespace"` Controller string `json:"controller"` Pod string `json:"pod"` Node string `json:"node"` Labels map[string]string `json:"labels"` Region string `json:"region"` Zone string `json:"zone"` NetworkTotalCost float64 `json:"networkCost"` NetworkCrossZoneCost float64 `json:"networkCrossZoneCost"` NetworkCrossRegionCost float64 `json:"networkCrossRegionCost"` NetworkInternetCost float64 `json:"networkInternetCost"` NetworkDetails NetworkDetailsSet `json:"networkDetails"` } func NewNetworkInsight(cluster string, namespace string, controller string, pod string, node string, labels map[string]string, region string, zone string, networkTotalCost, networkCrossZoneCost, networkCrossRegionCost, networkInternetCost float64, networkDetails map[string]*NetworkDetail) *NetworkInsight { if networkDetails == nil { networkDetails = make(map[string]*NetworkDetail, 0) } return &NetworkInsight{ Cluster: cluster, Namespace: namespace, Controller: controller, Pod: pod, Node: node, Labels: labels, Region: region, Zone: zone, NetworkTotalCost: networkTotalCost, NetworkCrossZoneCost: networkCrossZoneCost, NetworkCrossRegionCost: networkCrossRegionCost, NetworkInternetCost: networkInternetCost, NetworkDetails: networkDetails, } } func (ni *NetworkInsight) Clone() *NetworkInsight { if ni == nil { return nil } return &NetworkInsight{ Cluster: ni.Cluster, Namespace: ni.Namespace, Pod: ni.Pod, Node: ni.Node, Labels: ni.Labels, Region: ni.Region, Zone: ni.Zone, NetworkTotalCost: ni.NetworkTotalCost, NetworkCrossZoneCost: ni.NetworkCrossZoneCost, NetworkCrossRegionCost: ni.NetworkCrossRegionCost, NetworkInternetCost: ni.NetworkInternetCost, NetworkDetails: ni.NetworkDetails.Clone(), } } func (ni *NetworkInsight) add(that *NetworkInsight) { if ni == nil { log.Warnf("NetworkInsight.Add: trying to add a nil receiver") return } if ni.Cluster != that.Cluster { ni.Cluster = "" } if ni.Namespace != that.Namespace { ni.Namespace = "" } if ni.Pod != that.Pod { ni.Pod = "" } if ni.Controller != that.Controller { ni.Controller = "" } if ni.Node != that.Node { ni.Node = "" } if ni.Region != that.Region { ni.Region = "" } if ni.Zone != that.Zone { ni.Zone = "" } // TO-DO: Check for labels match if we support label in single cluster! ni.NetworkTotalCost += that.NetworkTotalCost ni.NetworkCrossZoneCost += that.NetworkCrossZoneCost ni.NetworkCrossRegionCost += that.NetworkCrossRegionCost ni.NetworkInternetCost += that.NetworkInternetCost ni.NetworkDetails.Combine(that.NetworkDetails) } // Key takes a list of NetworkInsightProperty and creates a "/" // seperated key based on the values of the requested properties. // Invalid values and empty slice are set to default key. func (ni *NetworkInsight) Key(props []NetworkInsightProperty) (string, error) { defaultString := fmt.Sprintf("%s/%s/%s", ni.Cluster, ni.Namespace, ni.Pod) if len(props) == 0 { return defaultString, nil } values := make([]string, len(props)) for i, prop := range props { switch prop { case NetworkInsightsNamespace: values[i] = ni.Namespace case NetworkInsightsPod: values[i] = ni.Pod case NetworkInsightsCluster: values[i] = ni.Cluster default: return defaultString, nil } } return strings.Join(values, "/"), nil } func (ni *NetworkInsight) GetTotalEgressByte() float64 { totalByte := 0.0 for _, nd := range ni.NetworkDetails { if nd == nil || nd.TrafficDirection != NetworkTrafficDirectionEgress { continue } totalByte += nd.Bytes } return totalByte } func (ni *NetworkInsight) GetTotalIngressByte() float64 { totalByte := 0.0 for _, nd := range ni.NetworkDetails { if nd == nil || nd.TrafficDirection != NetworkTrafficDirectionIngress { continue } totalByte += nd.Bytes } return totalByte } func (ni *NetworkInsight) SanitizeNaN() { if ni == nil { return } key, err := ni.Key([]NetworkInsightProperty{}) if err != nil { log.DedupedWarningf(5, "NetworkInsight: unable to perform santization of network insight for cluster: %s, namespace: %s, pod: %s", ni.Cluster, ni.Namespace, ni.Pod) } if math.IsNaN(ni.NetworkTotalCost) { log.DedupedWarningf(5, "NetworkInsight: Unexpected NaN found for NetworkTotalCost: name: %s", key) ni.NetworkTotalCost = 0 } if math.IsNaN(ni.NetworkCrossZoneCost) { log.DedupedWarningf(5, "NetworkInsight: Unexpected NaN found for NetworkCrossZoneCost: name: %s", key) ni.NetworkCrossZoneCost = 0 } if math.IsNaN(ni.NetworkCrossRegionCost) { log.DedupedWarningf(5, "NetworkInsight: Unexpected NaN found for NetworkCrossRegionCost: name: %s", key) ni.NetworkCrossRegionCost = 0 } if math.IsNaN(ni.NetworkInternetCost) { log.DedupedWarningf(5, "NetworkInsight: Unexpected NaN found for NetworkInternetCost: name: %s", key) ni.NetworkInternetCost = 0 } ni.NetworkDetails.SanitizeNaN() } func (ni *NetworkInsight) filterZeroCost() { if ni == nil { return } ni.NetworkDetails = ni.NetworkDetails.filterZeroCost() } func (ni *NetworkInsight) filterNetworkDetails(networkDetailFilter NetworkInsightDetailMatcher) { if ni == nil { log.DedupedWarningf(5, "NetworkInsight:filterNetworkDetails called on nil network insight") return } newNds := make(NetworkDetailsSet, 0) for key, nd := range ni.NetworkDetails { if networkDetailFilter.Matches(nd) { newNds[key] = nd } } ni.NetworkDetails = newNds } // SetWithNetworkInsightProperty sets the corresponding property // variable in the struct with the value passed to the function. func (ni *NetworkInsight) SetWithNetworkInsightProperty(property NetworkInsightProperty, value interface{}) error { switch property { case NetworkInsightsCluster: ni.Cluster = value.(string) case NetworkInsightsNamespace: ni.Namespace = value.(string) case NetworkInsightsPod: ni.Pod = value.(string) } return fmt.Errorf("unsupported property: %s", string(property)) } type NetworkInsightSet struct { NetworkInsights map[string]*NetworkInsight `json:"networkInsights"` Window Window `json:"window"` } // NewNetworkInsightSet instantiates a new NetworkInsights set and, optionally, inserts // the given list of NetworkInsight func NewNetworkInsightSet(start, end time.Time, networkInsight ...*NetworkInsight) *NetworkInsightSet { nis := &NetworkInsightSet{ NetworkInsights: make(map[string]*NetworkInsight, 0), Window: NewWindow(&start, &end), } for _, ni := range networkInsight { nis.Insert(ni, []NetworkInsightProperty{}) } return nis } func (nis *NetworkInsightSet) Add(that *NetworkInsightSet, keyProperties []NetworkInsightProperty) (*NetworkInsightSet, error) { if (nis == nil || len(nis.NetworkInsights) == 0) && (that == nil || len(that.NetworkInsights) == 0) { return nis, nil } if nis == nil || len(nis.NetworkInsights) == 0 { return that, nil } if that == nil || len(that.NetworkInsights) == 0 { return that, nil } start := *nis.Window.Start() end := *nis.Window.End() if that.Window.Start().Before(start) { start = *that.Window.Start() } if that.Window.End().After(end) { end = *that.Window.End() } acc := &NetworkInsightSet{ NetworkInsights: make(map[string]*NetworkInsight, len(nis.NetworkInsights)), Window: NewClosedWindow(start, end), } for _, ni := range nis.NetworkInsights { err := acc.Insert(ni, keyProperties) if err != nil { return nil, err } } for _, ni := range that.NetworkInsights { err := acc.Insert(ni, keyProperties) if err != nil { return nil, err } } return acc, nil } func (nis *NetworkInsightSet) Insert(that *NetworkInsight, aggregateBy []NetworkInsightProperty) error { if nis == nil { return fmt.Errorf("cannot insert into nil networkInsightSet") } if nis.NetworkInsights == nil { nis.NetworkInsights = map[string]*NetworkInsight{} } key, err := that.Key(aggregateBy) if err != nil { return fmt.Errorf("unable to generate key for aggregation: %v", err) } if _, ok := nis.NetworkInsights[key]; !ok { nis.NetworkInsights[key] = that } else { nis.NetworkInsights[key].add(that) } return nil } func (nis *NetworkInsightSet) Clone() *NetworkInsightSet { if nis == nil { return nil } networkInsights := make(map[string]*NetworkInsight, len(nis.NetworkInsights)) for k, v := range nis.NetworkInsights { networkInsights[k] = v.Clone() } return &NetworkInsightSet{ NetworkInsights: networkInsights, Window: nis.Window.Clone(), } } func (nis *NetworkInsightSet) GetWindow() Window { return nis.Window } func (nis *NetworkInsightSet) IsValid() bool { if !nis.IsEmpty() { return false } if nis.Window.IsOpen() { return false } return true } func (nis *NetworkInsightSet) IsEmpty() bool { if nis == nil || len(nis.NetworkInsights) == 0 { return true } return false } func (nis *NetworkInsightSet) AggregateBy(aggregateBy []NetworkInsightProperty) error { if nis.IsEmpty() { return nil } aggSet := &NetworkInsightSet{} for _, ni := range nis.NetworkInsights { err := aggSet.Insert(ni, aggregateBy) if err != nil { return fmt.Errorf("NetworkInsightSet:AggregateBy failed with err: %v", err) } } nis.NetworkInsights = aggSet.NetworkInsights return nil } func (nis *NetworkInsightSet) Accumulate(that *NetworkInsightSet, keyProperties []NetworkInsightProperty) (*NetworkInsightSet, error) { if nis.IsEmpty() { return that.Clone(), nil } if that.IsEmpty() { return nis.Clone(), nil } start := nis.Window.Start() end := nis.Window.End() if start.After(*that.Window.Start()) { start = that.Window.Start() } if end.Before(*that.Window.End()) { end = that.Window.End() } newNis := nis.Clone() newNis.Window = NewClosedWindow(*start, *end) for _, ni := range that.NetworkInsights { err := newNis.Insert(ni, keyProperties) if err != nil { return nil, err } } return newNis, nil } func (nis *NetworkInsightSet) Length() int { if nis == nil { return 0 } return len(nis.NetworkInsights) } func (nis *NetworkInsightSet) FilterOn(filter filter.Filter) error { if nis.IsEmpty() { return fmt.Errorf("NetworkInsightSet:FilterOn called on empty network insight set") } var networkInsightFilter NetworkInsightMatcher if filter == nil { networkInsightFilter = &matcher.AllPass[*NetworkInsight]{} } else { compiler := NewNetworkInsightMatchCompiler() var err error networkInsightFilter, err = compiler.Compile(filter) if err != nil { return fmt.Errorf("compiling filter '%s': %w", ast.ToPreOrderShortString(filter), err) } } if networkInsightFilter == nil { return fmt.Errorf("unexpected nil filter") } for key, ni := range nis.NetworkInsights { if ni == nil { continue } if !networkInsightFilter.Matches(ni) { delete(nis.NetworkInsights, key) } } return nil } // Resolution returns the NetworkInsightSet's window duration func (nis *NetworkInsightSet) Resolution() time.Duration { if nis == nil { return time.Duration(0) } return nis.Window.Duration() } func (nis *NetworkInsightSet) FilterNetworkDetails(opts *NetworkDetailsOptions) error { if nis == nil { return fmt.Errorf("filterNetworkDetails called on nil network insight set") } if opts == nil { return nil } var networkDetailFilter NetworkInsightDetailMatcher if opts.FilterNetworkDetails == nil { networkDetailFilter = &matcher.AllPass[*NetworkDetail]{} } else { compiler := NewNetworkInsightDetailMatchCompiler() var err error networkDetailFilter, err = compiler.Compile(opts.FilterNetworkDetails) if err != nil { return fmt.Errorf("compiling filter '%s': %w", ast.ToPreOrderShortString(opts.FilterNetworkDetails), err) } } if networkDetailFilter == nil { return fmt.Errorf("unexpected nil filter") } for _, ni := range nis.NetworkInsights { // filter network details that satisfy the // network detail filter ni.filterNetworkDetails(networkDetailFilter) // filter zero cost network details if !opts.ShowZeroCost { ni.filterZeroCost() } } return nil } func (nis *NetworkInsightSet) SanitizeNaN() { if nis == nil { return } for _, ni := range nis.NetworkInsights { ni.SanitizeNaN() } } type NetworkInsightSetRange struct { sync.RWMutex NetworkInsightsSet []*NetworkInsightSet `json:"networkInsightSet"` Window Window `json:"window"` } func NewNetworkInsightSetRange(window Window, nis ...*NetworkInsightSet) *NetworkInsightSetRange { return &NetworkInsightSetRange{ NetworkInsightsSet: nis, Window: window, } } func (nisr *NetworkInsightSetRange) AggregateBy(aggregateBy []NetworkInsightProperty) error { if nisr == nil || len(nisr.NetworkInsightsSet) == 0 { return nil } if nisr.Window.IsOpen() { return fmt.Errorf("cannot aggregate a NetworkInsightSetRange with an open window") } tempNis := &NetworkInsightSetRange{NetworkInsightsSet: []*NetworkInsightSet{}} for _, ni := range nisr.NetworkInsightsSet { err := ni.AggregateBy(aggregateBy) if err != nil { return err } tempNis.NetworkInsightsSet = append(tempNis.NetworkInsightsSet, ni) } nisr.NetworkInsightsSet = tempNis.NetworkInsightsSet return nil } func (nisr *NetworkInsightSetRange) Append(that *NetworkInsightSet) { if nisr == nil { log.DedupedWarningf(5, "NetworkInsightSetRange:Append called on nil Network Insight Set Range") return } nisr.Lock() defer nisr.Unlock() nisr.NetworkInsightsSet = append(nisr.NetworkInsightsSet, that) // Adjust window start := nisr.Window.Start() end := nisr.Window.End() if nisr.Window.Start() == nil || (that.Window.Start() != nil && that.Window.Start().Before(*nisr.Window.Start())) { start = that.Window.Start() } if nisr.Window.End() == nil || (that.Window.End() != nil && that.Window.End().After(*nisr.Window.End())) { end = that.Window.End() } nisr.Window = NewClosedWindow(*start, *end) } func (nisr *NetworkInsightSetRange) Clone() *NetworkInsightSetRange { if nisr == nil { return nil } nisrClone := NewNetworkInsightSetRange(nisr.Window) for _, nis := range nisr.NetworkInsightsSet { nisClone := nis.Clone() nisrClone.Append(nisClone) } return nisrClone } func (nisr *NetworkInsightSetRange) accumulateByNone(keyProperties []NetworkInsightProperty) (*NetworkInsightSetRange, error) { return nisr.Clone(), nil } func (nisr *NetworkInsightSetRange) accumulateByAll(keyProperties []NetworkInsightProperty) (*NetworkInsightSetRange, error) { nis, err := nisr.newAccumulation(keyProperties) if err != nil { return nil, fmt.Errorf("error accumulating NetworkInsightSetRange:%w", err) } accumulated := NewNetworkInsightSetRange(nisr.Window, nis) return accumulated, nil } func (nisr *NetworkInsightSetRange) accumulateByHour(keyProperties []NetworkInsightProperty) (*NetworkInsightSetRange, error) { if nisr == nil { return nil, fmt.Errorf("NetworkInsightSetRange:accumulateByHour called on nil set range") } // ensure that the network insight set have a 1-hour window and if a set exists duration := nisr.Window.Duration() if len(nisr.NetworkInsightsSet) > 0 && duration != time.Hour { return nil, fmt.Errorf("window duration must equal 1 hour; got:%s", duration.String()) } return nisr.Clone(), nil } func (nisr *NetworkInsightSetRange) accumulate(keyProperties []NetworkInsightProperty) (*NetworkInsightSet, error) { if nisr == nil { return nil, fmt.Errorf("NetworkInsightSetRange:accumulate called on nil set range") } var result *NetworkInsightSet var err error nisr.RLock() defer nisr.RUnlock() for _, ni := range nisr.NetworkInsightsSet { result, err = result.Add(ni, keyProperties) if err != nil { return nil, err } } return result, nil } func (nisr *NetworkInsightSetRange) accumulateByDay(keyProperties []NetworkInsightProperty) (*NetworkInsightSetRange, error) { if nisr == nil { return nil, fmt.Errorf("NetworkInsightSetRange:accumulateByDay called on nil set range") } // if the network insight set window is 1-day, just return the existing allocation set range duration := nisr.Window.Duration() if len(nisr.NetworkInsightsSet) > 0 && duration == timeutil.Day { return nisr, nil } var toAccumulate *NetworkInsightSetRange result := NewNetworkInsightSetRange(NewWindow(nil, nil)) for i, nis := range nisr.NetworkInsightsSet { if nis.Window.Duration() != time.Hour { return nil, fmt.Errorf("window duration must equal 1 hour; got:%s", nis.Window.Duration()) } hour := nis.Window.Start().Hour() if toAccumulate == nil { toAccumulate = NewNetworkInsightSetRange(NewWindow(nil, nil)) nis = nis.Clone() } toAccumulate.Append(nis) nis, err := toAccumulate.accumulate(keyProperties) if err != nil { return nil, fmt.Errorf("error accumulating result: %s", err) } if nis == nil { continue } toAccumulate = NewNetworkInsightSetRange(nis.Window, nis) if hour == 23 || i == len(nisr.NetworkInsightsSet)-1 { if length := len(toAccumulate.NetworkInsightsSet); length != 1 { return nil, fmt.Errorf("failed accumulation, detected %d sets instead of 1", length) } result.Append(toAccumulate.NetworkInsightsSet[0]) toAccumulate = nil } } return result, nil } func (nisr *NetworkInsightSetRange) accumulateByWeek(keyProperties []NetworkInsightProperty) (*NetworkInsightSetRange, error) { if nisr == nil { return nil, fmt.Errorf("NetworkInsightSetRange:accumulateByWeek called on nil set range") } var toAccumulate *NetworkInsightSetRange result := NewNetworkInsightSetRange(NewWindow(nil, nil)) for i, nis := range nisr.NetworkInsightsSet { if nis.Window.Duration() != timeutil.Day { return nil, fmt.Errorf("window duration must equal 24 hours; got:%s", nis.Window.Duration()) } dayOfWeek := nis.Window.Start().Weekday() if toAccumulate == nil { toAccumulate = NewNetworkInsightSetRange(NewWindow(nil, nil)) nis = nis.Clone() } toAccumulate.Append(nis) nis, err := toAccumulate.accumulate(keyProperties) if err != nil { return nil, fmt.Errorf("error accumulating result: %s", err) } if nis == nil { continue } toAccumulate = NewNetworkInsightSetRange(nis.Window, nis) if dayOfWeek == time.Saturday || i == len(nisr.NetworkInsightsSet)-1 { if length := len(toAccumulate.NetworkInsightsSet); length != 1 { return nil, fmt.Errorf("failed accumulation, detected %d sets instead of 1", length) } result.Append(toAccumulate.NetworkInsightsSet[0]) toAccumulate = nil } } return result, nil } func (nisr *NetworkInsightSetRange) accumulateByMonth(keyProperties []NetworkInsightProperty) (*NetworkInsightSetRange, error) { if nisr == nil { return nil, fmt.Errorf("NetworkInsightSetRange:accumulateByMonth called on nil set range") } var toAccumulate *NetworkInsightSetRange result := NewNetworkInsightSetRange(NewWindow(nil, nil)) for i, nis := range nisr.NetworkInsightsSet { if nis.Window.Duration() != timeutil.Day { return nil, fmt.Errorf("window duration must equal 24 hours; got:%s", nis.Window.Duration()) } _, month, _ := nis.Window.Start().Date() _, nextDayMonth, _ := nis.Window.Start().Add(time.Hour * 24).Date() if toAccumulate == nil { toAccumulate = NewNetworkInsightSetRange(NewWindow(nil, nil)) nis = nis.Clone() } toAccumulate.Append(nis) nis, err := toAccumulate.accumulate(keyProperties) if err != nil { return nil, fmt.Errorf("error accumulating result: %s", err) } if nis == nil { continue } toAccumulate = NewNetworkInsightSetRange(nis.Window, nis) if month != nextDayMonth || i == len(nisr.NetworkInsightsSet)-1 { if length := len(toAccumulate.NetworkInsightsSet); length != 1 { return nil, fmt.Errorf("failed accumulation, detected %d sets instead of 1", length) } result.Append(toAccumulate.NetworkInsightsSet[0]) toAccumulate = nil } } return result, nil } func (nisr *NetworkInsightSetRange) Accumulate(accumulateBy AccumulateOption, keyProperties []NetworkInsightProperty) (*NetworkInsightSetRange, error) { if nisr == nil { return nil, fmt.Errorf("NetworkInsightSetRange:Accumulate called on nil set range") } switch accumulateBy { case AccumulateOptionNone: return nisr.accumulateByNone(keyProperties) case AccumulateOptionAll: return nisr.accumulateByAll(keyProperties) case AccumulateOptionHour: return nisr.accumulateByHour(keyProperties) case AccumulateOptionDay: return nisr.accumulateByDay(keyProperties) case AccumulateOptionWeek: return nisr.accumulateByWeek(keyProperties) case AccumulateOptionMonth: return nisr.accumulateByMonth(keyProperties) default: // ideally, this should never happen return nil, fmt.Errorf("unexpected error, invalid accumulateByType: %s", accumulateBy) } } func (nisr *NetworkInsightSetRange) newAccumulation(keyProperties []NetworkInsightProperty) (*NetworkInsightSet, error) { if nisr == nil { return nil, fmt.Errorf("nil NetworkInsightSetRange in accumulation") } var networkInsigthSet *NetworkInsightSet var err error if len(nisr.NetworkInsightsSet) == 0 { return nil, fmt.Errorf("NetworkInsightSetRange has empty NetworkInsightSet in accumulation") } for _, nis := range nisr.NetworkInsightsSet { if networkInsigthSet == nil { networkInsigthSet = nis.Clone() continue } networkInsigthSet, err = networkInsigthSet.Accumulate(nis, keyProperties) if err != nil { return nil, err } } return networkInsigthSet, nil } func (nisr *NetworkInsightSetRange) FilterOn(filter filter.Filter) error { if nisr == nil { return fmt.Errorf("filter called on nil networkInsightSetRange") } for _, nis := range nisr.NetworkInsightsSet { err := nis.FilterOn(filter) if err != nil { return fmt.Errorf("unable to filter nis for window: %s with err: %v", nis.Window.String(), err) } } return nil } // FilterNetworkDetails for a given network insight set with the options applied. // When ShowZeroCost is set to false, all the network detail interactions with // zero cost are dropped and based on the applied filter only. func (nisr *NetworkInsightSetRange) FilterNetworkDetails(opts *NetworkDetailsOptions) error { if opts == nil { return nil } if nisr == nil { return fmt.Errorf("filter called on nil networkInsightSetRange") } for _, nis := range nisr.NetworkInsightsSet { err := nis.FilterNetworkDetails(opts) if err != nil { return fmt.Errorf("unable to filter network details in nis for window: %s with err: %v", nis.Window.String(), err) } } return nil }