github.com/m3db/m3@v1.5.0/scripts/comparator/utils/compare_utilities_regression.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package utils
    22  
    23  import (
    24  	"encoding/json"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"net/url"
    28  	"os"
    29  	"path/filepath"
    30  
    31  	"github.com/m3db/m3/src/cmd/services/m3comparator/main/parser"
    32  
    33  	"go.uber.org/zap"
    34  )
    35  
    36  // RegressionQuery is the JSON representation of a query to be compared.
    37  type RegressionQuery struct {
    38  	Name         string          `json:"name"`
    39  	Query        string          `json:"query"`
    40  	StartSeconds int64           `json:"startSeconds"`
    41  	EndSeconds   int64           `json:"endSeconds"`
    42  	Step         int             `json:"step"`
    43  	Retries      int             `json:"retries"`
    44  	Data         []parser.Series `json:"data"`
    45  }
    46  
    47  func parseRegressionFileToQueries(
    48  	fileName string,
    49  	log *zap.Logger,
    50  ) (RegressionQuery, error) {
    51  	file, err := os.Open(fileName)
    52  	if err != nil {
    53  		log.Error("could not open file", zap.Error(err))
    54  		return RegressionQuery{}, err
    55  	}
    56  
    57  	defer file.Close()
    58  	buf, err := ioutil.ReadAll(file)
    59  	if err != nil {
    60  		log.Error("could not read file", zap.Error(err))
    61  		return RegressionQuery{}, err
    62  	}
    63  
    64  	var query RegressionQuery
    65  	if err := json.Unmarshal(buf, &query); err != nil {
    66  		log.Error("could not unmarshal regression query", zap.Error(err))
    67  		return RegressionQuery{}, err
    68  	}
    69  
    70  	return query, err
    71  }
    72  
    73  // PromQLRegressionQueryGroup is a PromQLQueryGroup with a given data set.
    74  type PromQLRegressionQueryGroup struct {
    75  	PromQLQueryGroup
    76  	Retries int
    77  	Data    []parser.Series
    78  }
    79  
    80  func (q RegressionQuery) constructPromQL() PromQLRegressionQueryGroup {
    81  	values := make(url.Values)
    82  	values.Add("query", q.Query)
    83  	values.Add("step", fmt.Sprint(q.Step))
    84  	values.Add("start", fmt.Sprint(q.StartSeconds))
    85  	values.Add("end", fmt.Sprint(q.EndSeconds))
    86  	query := "/api/v1/query_range?" + values.Encode()
    87  
    88  	retries := 1
    89  	if q.Retries > 1 {
    90  		retries = q.Retries
    91  	}
    92  
    93  	return PromQLRegressionQueryGroup{
    94  		PromQLQueryGroup: PromQLQueryGroup{
    95  			QueryGroup: q.Name,
    96  			Queries:    []string{query},
    97  		},
    98  		Data:    q.Data,
    99  		Retries: retries,
   100  	}
   101  }
   102  
   103  func parseRegressionFileToPromQLQueryGroup(
   104  	fileName string,
   105  	log *zap.Logger,
   106  ) (PromQLRegressionQueryGroup, error) {
   107  	query, err := parseRegressionFileToQueries(fileName, log)
   108  	if err != nil {
   109  		return PromQLRegressionQueryGroup{}, err
   110  	}
   111  
   112  	return query.constructPromQL(), nil
   113  }
   114  
   115  // ParseRegressionFilesToPromQLQueryGroup parses a directory with
   116  // regression query files into PromQL query groups.
   117  func ParseRegressionFilesToPromQLQueryGroup(
   118  	directory string,
   119  	log *zap.Logger,
   120  ) ([]PromQLRegressionQueryGroup, error) {
   121  	files, err := ioutil.ReadDir(directory)
   122  	if err != nil {
   123  		log.Info("could not read directory")
   124  		return nil, err
   125  	}
   126  
   127  	if len(files) == 0 {
   128  		log.Info("no files in directory")
   129  		return nil, nil
   130  	}
   131  
   132  	groups := make([]PromQLRegressionQueryGroup, 0, len(files))
   133  	for _, f := range files {
   134  		filePath := filepath.Join(directory, f.Name())
   135  		if f.IsDir() {
   136  			log.Info("skipping file", zap.String("filePath", filePath))
   137  		}
   138  
   139  		group, err := parseRegressionFileToPromQLQueryGroup(filePath, log)
   140  		if err != nil {
   141  			log.Error("failed to parse file", zap.String("path", filePath), zap.Error(err))
   142  			return nil, err
   143  		}
   144  
   145  		groups = append(groups, group)
   146  	}
   147  
   148  	return groups, nil
   149  }