...

Source file src/edge-infra.dev/cmd/edge/monitoring/metermaid/cli/align.go

Documentation: edge-infra.dev/cmd/edge/monitoring/metermaid/cli

     1  package cli
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/peterbourgon/ff/v3/ffcli"
    10  
    11  	"edge-infra.dev/pkg/lib/gcp/monitoring/metrics"
    12  	"edge-infra.dev/pkg/lib/gcp/monitoring/monutil"
    13  )
    14  
    15  type alignArgsT struct {
    16  	noPrompt bool
    17  }
    18  
    19  var alignArgs alignArgsT
    20  var alignFlagSet = newAlignFlagSet(&alignArgs)
    21  
    22  //nolint:golint,unused  // Global flags requires a flagset passthrough and allows for future action specific flags
    23  func newAlignFlagSet(alignArgs *alignArgsT) *flag.FlagSet {
    24  	alignf := newFlagSet("align")
    25  	alignf.BoolVar(&alignArgs.noPrompt, "no-prompt", false, "Determines whether the user will be prompted to confirm each descriptor alignment action.")
    26  	return alignf
    27  }
    28  
    29  var alignCmd = &ffcli.Command{
    30  	Name:       "align",
    31  	ShortUsage: "align [flags]",
    32  	ShortHelp:  "align metric descriptor labels",
    33  	LongHelp: strings.TrimSpace(`
    34  Aligns metricDescriptor labels from two or more projects.
    35  `),
    36  	FlagSet: withGlobalFlags(alignFlagSet),
    37  	Exec:    runAlign,
    38  }
    39  
    40  func runAlign(ctx context.Context, args []string) error {
    41  	var err error
    42  
    43  	if len(args) > 0 {
    44  		Fatalf("too many non-flag arguments: %q", args)
    45  	}
    46  	if !checkAlignFlags() || !checkGlobalFlags() {
    47  		return flag.ErrHelp
    48  	}
    49  
    50  	// retrieve list of metric descriptors for all projectIDs specified
    51  	projects := strings.Split(strings.ReplaceAll(projectID, " ", ""), ",")
    52  	d, err := retrieveDescriptors(ctx, projects)
    53  	if err != nil {
    54  		return Errorf("failed to metricDescriptors for multiple projects: %w", err)
    55  	}
    56  
    57  	// necessary when specifying multiple projects as foreman is a multi-project source
    58  	if filterForeman {
    59  		projects := strings.Split(strings.ReplaceAll(projectID, " ", ""), ",")
    60  		d, err = d.FilterProjects(projects)
    61  		if err != nil {
    62  			return err
    63  		}
    64  	}
    65  
    66  	// compare the metric descriptors
    67  	cd := d.Compare()
    68  
    69  	// create the list of metric descriptors to be aligned
    70  	var ads alignDescriptors
    71  	for i := 0; i < len(cd.Descriptor); i++ {
    72  		if len(cd.Descriptor[i].DiffLabels) > 0 {
    73  			ads = append(ads, alignDescriptor{
    74  				Name:         cd.Descriptor[i].Name,
    75  				CommonLabels: cd.Descriptor[i].CommonLabels,
    76  				DiffLabels:   cd.Descriptor[i].DiffLabels,
    77  			})
    78  		}
    79  	}
    80  
    81  	ar, err := ads.align(ctx)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	if jsonFormat { // output report to json
    87  		_ = metrics.TmplJSON.Execute(Stdout, ar)
    88  	} else if !silent { // output visual layout of align report
    89  		monutil.ClearTerminal()
    90  		_ = metrics.TmplAlignReport.Execute(Stdout, ar)
    91  		fmt.Printf("\n\n%s", monutil.White(mmBroomTitle))
    92  	}
    93  	return nil
    94  }
    95  
    96  // checkCompareFlags validates the required Compare subcommand flags have been provided.
    97  func checkAlignFlags() bool {
    98  	return true
    99  }
   100  
   101  type alignDescriptors []alignDescriptor
   102  type alignDescriptor metrics.AlignDescriptor
   103  
   104  // parse the alignDescriptors slice run align attempt on each descriptor, returns AlignReport
   105  func (ads alignDescriptors) align(ctx context.Context) (metrics.AlignReport, error) {
   106  	var ar metrics.AlignReport
   107  	for i := 0; i < len(ads); i++ {
   108  		ar.Misaligned++
   109  		a, err := ads[i].align(ctx)
   110  		if err != nil && a.Name == "" {
   111  			ar.Attempted++
   112  			ar.Failed = append(ar.Failed, ads[i].Name)
   113  		} else if err != nil && a.Name != "" {
   114  			ar.Attempted++
   115  			ar.Skipped = append(ar.Skipped, a.Name)
   116  		} else {
   117  			ar.Completed++
   118  			ar.Aligned = append(ar.Aligned, a.Name)
   119  		}
   120  	}
   121  
   122  	if (ar.Attempted + ar.Completed) != ar.Misaligned {
   123  		return ar, fmt.Errorf("the align result count of Attempted (%d) + Completed (%d) does not equal the total Misaligned (%d) count", ar.Attempted, ar.Completed, ar.Misaligned)
   124  	}
   125  	return ar, nil
   126  }
   127  
   128  // align the metric descriptor instance
   129  func (ad alignDescriptor) align(ctx context.Context) (*metrics.Descriptor, error) {
   130  	project := metrics.ParseProjectID(ad.Name)
   131  	metric := metrics.ParseMetricName(ad.Name)
   132  	prefix := metrics.ParsePrefix(ad.Name)
   133  	allLabels := append(ad.CommonLabels, ad.DiffLabels...)
   134  
   135  	// create a new metrics client
   136  	client, err := metrics.New(ctx, project)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	// get the metric descriptor object by name
   142  	d, err := client.GetDescriptor(prefix, metric)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	var aConfirm = false
   148  	if !silent && !alignArgs.noPrompt {
   149  		// build confirmation prompt string and prompt user
   150  		monutil.ClearTerminal()
   151  		q := *new(strings.Builder)
   152  		_ = metrics.TmplAlignConfirm.Execute(&q, ad)
   153  		aConfirm = monutil.PromptYesNoCancel(q.String())
   154  		fmt.Println()
   155  	}
   156  
   157  	// run the metric descriptor label alignment
   158  	if silent || alignArgs.noPrompt || aConfirm {
   159  		if client.AddTimeSeriesLabels(d, allLabels) {
   160  			r, err := client.GetDescriptor(prefix, metric)
   161  			if err != nil {
   162  				return nil, err
   163  			}
   164  			return r, nil
   165  		}
   166  	}
   167  
   168  	// return the metric descriptor info that was skipped
   169  	return &metrics.Descriptor{
   170  		Name:         ad.Name,
   171  		CommonLabels: ad.CommonLabels,
   172  		DiffLabels:   ad.DiffLabels,
   173  	}, fmt.Errorf("skipping metric %s descriptor alignment", ad.Name)
   174  }
   175  

View as plain text