...

Source file src/edge-infra.dev/pkg/edge/monitoring/billman/costs/costs.go

Documentation: edge-infra.dev/pkg/edge/monitoring/billman/costs

     1  // Package costs provides functions for calculating and
     2  // showing GCP costs
     3  package costs
     4  
     5  import (
     6  	"fmt"
     7  	"math"
     8  	"os"
     9  	"strings"
    10  	"text/tabwriter"
    11  
    12  	"edge-infra.dev/pkg/edge/monitoring/billman/edgesql"
    13  )
    14  
    15  // MetricData contains the number of metric samples and the
    16  // estimated GCP cost for those samples.
    17  type MetricData struct {
    18  	Samples int64
    19  	Cost    float64
    20  }
    21  
    22  // LogNodeData contains the number of log bytes
    23  // and the estimated GCP cost for ingested bytes
    24  // from k8s-container
    25  type LogContainerData struct {
    26  	Bytes int64
    27  	Cost  float64
    28  }
    29  
    30  // LogNodeData contains the number of log bytes
    31  // and the estimated GCP cost for ingested bytes
    32  // from k8s-node
    33  type LogNodeData struct {
    34  	Bytes int64
    35  	Cost  float64
    36  }
    37  
    38  // LogData contains the number of log bytes from both node
    39  // & container and the total estimated GCP cost for those
    40  // ingested bytes.
    41  type LogData struct {
    42  	Bytes int64
    43  	Cost  float64
    44  }
    45  
    46  // Options let you control the table output. i.e., showing
    47  // or hiding the table header, printing as tab or csv.
    48  type Options struct {
    49  	Output         string // csv, tab
    50  	ShowDisclaimer bool
    51  	ShowHeader     bool
    52  }
    53  
    54  // Billing is used by PrintClusterCosts to control the
    55  // output of the CLI.  All fields in the struct are required.
    56  type Billing struct {
    57  	Cluster edgesql.EdgeCluster
    58  	LogContainerData
    59  	LogNodeData
    60  	LogData
    61  	MetricData
    62  	Options
    63  	DisplayType string // e.g.: "all", "metrics", "logs"
    64  }
    65  
    66  // LogCosts calculates the estimated GCP costs for logs
    67  // ingested.
    68  // ref: https://cloud.google.com/stackdriver/pricing#binary-units:
    69  // For pricing purposes, all units are treated as binary measures,
    70  // for example, as mebibytes (MiB, or 2^20 bytes) or gibibytes (GiB, or 2^30 bytes).
    71  func LogCosts(bytes int64, rate float64) float64 {
    72  	gb := float64(bytes) / 1024 / 1024 / 1024
    73  	dollars := rate * gb
    74  	cost := math.Ceil(dollars*100) / 100
    75  	return cost
    76  }
    77  
    78  // MetricCosts calculates the estimated GCP costs for metric
    79  // samples ingested. As noted on the
    80  // [pricing summary page](https://cloud.google.com/stackdriver/pricing#monitoring-pricing-summary)
    81  // metrics have a tiered billing rate but these rates are
    82  // tiered on a per-project basis, rather than per-cluster.
    83  // This function only provides the billing estimates for
    84  // metrics // using the non-tiered base rate of
    85  // $0.15/million samples.  If a given cluster has over 50
    86  // billion samples during the interval that this cli is
    87  // executed then the estimates with this function might be
    88  // slightly higher than what google would charge for during
    89  // a monthly billing cycle.
    90  func MetricCosts(samples int64, rate float64) float64 {
    91  	millionSamples := float64(samples) / 1000000
    92  	dollars := rate * millionSamples
    93  	cost := math.Ceil(dollars*100) / 100
    94  	return cost
    95  }
    96  
    97  func printDisclamer() {
    98  	fmt.Fprintln(os.Stderr, `
    99  ******************
   100  * Note: The results shown below are only estimates. 
   101  * Please read our billing disclaimer at https://docs.edge-infra.dev/edge/observability/Billing/billing/
   102  ******************`)
   103  }
   104  
   105  // PrintClusterCosts prints to stdout a table or csv
   106  // showing log and or metric costs for a given cluster.
   107  // Output options can be controlled by setting the
   108  // options
   109  func PrintClusterCosts(b Billing) {
   110  	if b.Options.ShowDisclaimer {
   111  		printDisclamer()
   112  	}
   113  
   114  	w := tabwriter.NewWriter(os.Stdout, 5, 2, 2, ' ', 0)
   115  
   116  	baseCols := []string{"CLUSTER_NAME", "CLUSTER_EDGE_ID", "BANNER_NAME", "PROJECT_ID"}
   117  	baseFmt := []string{"%s", "%s", "%s", "%s"}
   118  
   119  	logCols := []string{"LOG_CONTAINER_BYTES", "LOG_CONTAINER_COST", "LOG_NODE_BYTES", "LOG_NODE_COST", "LOG_BYTES", "LOG_COST"}
   120  	logFmt := []string{"%d", "%.2f", "%d", "%.2f", "%d", "%.2f"}
   121  
   122  	metricCols := []string{"METRIC_SAMPLES", "METRIC_COST"}
   123  	metricFmt := []string{"%d", "%.2f"}
   124  
   125  	totalCol := []string{"TOTAL"}
   126  	totalFmt := []string{"%.2f\n"}
   127  
   128  	var separator string
   129  	switch b.Options.Output {
   130  	case "csv":
   131  		separator = ","
   132  	default:
   133  		separator = "\t"
   134  	}
   135  
   136  	switch b.DisplayType {
   137  	case "all":
   138  		c := append(append(append(baseCols, logCols...), metricCols...), totalCol...)
   139  		cFmt := append(append(append(baseFmt, logFmt...), metricFmt...), totalFmt...)
   140  		cols := fmt.Sprint(strings.Join(c, separator))
   141  		fmtStr := fmt.Sprint(strings.Join(cFmt, separator))
   142  		if b.Options.ShowHeader {
   143  			fmt.Fprintln(w, cols)
   144  		}
   145  		fmt.Fprintf(w, fmtStr,
   146  			b.Cluster.ClusterName,
   147  			b.Cluster.ClusterEdgeID,
   148  			b.Cluster.BannerName,
   149  			b.Cluster.ProjectID,
   150  			b.LogContainerData.Bytes,
   151  			b.LogContainerData.Cost,
   152  			b.LogNodeData.Bytes,
   153  			b.LogNodeData.Cost,
   154  			b.LogData.Bytes,
   155  			b.LogData.Cost,
   156  			b.MetricData.Samples,
   157  			b.MetricData.Cost,
   158  			b.LogData.Cost+b.MetricData.Cost,
   159  		)
   160  	case "logs":
   161  		c := append(baseCols, logCols...)
   162  		cFmt := append(baseFmt, logFmt...)
   163  		cols := fmt.Sprint(strings.Join(c, separator))
   164  		fmtStr := fmt.Sprint(strings.Join(cFmt, separator))
   165  		if b.ShowHeader {
   166  			fmt.Fprintln(w, cols)
   167  		}
   168  		fmt.Fprintf(w, fmtStr+"\n",
   169  			b.Cluster.ClusterName,
   170  			b.Cluster.ClusterEdgeID,
   171  			b.Cluster.BannerName,
   172  			b.Cluster.ProjectID,
   173  			b.LogContainerData.Bytes,
   174  			b.LogContainerData.Cost,
   175  			b.LogNodeData.Bytes,
   176  			b.LogNodeData.Cost,
   177  			b.LogData.Bytes,
   178  			b.LogData.Cost,
   179  		)
   180  	case "metrics":
   181  		c := append(baseCols, metricCols...)
   182  		cFmt := append(baseFmt, metricFmt...)
   183  		cols := fmt.Sprint(strings.Join(c, separator))
   184  		fmtStr := fmt.Sprint(strings.Join(cFmt, separator))
   185  		if b.Options.ShowHeader {
   186  			fmt.Fprintln(w, cols)
   187  		}
   188  		fmt.Fprintf(w, fmtStr+"\n",
   189  			b.Cluster.ClusterName,
   190  			b.Cluster.ClusterEdgeID,
   191  			b.Cluster.BannerName,
   192  			b.Cluster.ProjectID,
   193  			b.MetricData.Samples,
   194  			b.MetricData.Cost,
   195  		)
   196  	}
   197  
   198  	w.Flush()
   199  }
   200  

View as plain text