github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/cmd/reproxytool/usage2csv/usage2csv.go (about)

     1  // Copyright 2023 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // Package usage2CSV is used to parse the usage data from reproxy.INFO log file
    16  // to a CSV file, ordered by timestamp. The output csv file will simply append a
    17  // csv suffix after the full name of the input reproxy.INFO file.
    18  //
    19  
    20  package usage2csv
    21  
    22  import (
    23  	"bufio"
    24  	"encoding/csv"
    25  	"io"
    26  	"os"
    27  	"regexp"
    28  	"sort"
    29  	"strconv"
    30  	"strings"
    31  
    32  	log "github.com/golang/glog"
    33  )
    34  
    35  var (
    36  	usages     = []*usage{}
    37  	usageRegex = regexp.MustCompile(`Resource Usage: map\[(.*?)\]`)
    38  )
    39  
    40  type usage struct {
    41  	timestamp, CPUPct, MemResMbs, MemVirtMbs, MemPct, PeakNumActions int64
    42  }
    43  
    44  func unmarshall(s string) *usage {
    45  	var u usage
    46  	fields := strings.Split(s, " ")
    47  	for _, field := range fields {
    48  		kv := strings.Split(field, ":")
    49  		key := kv[0]
    50  		value, err := strconv.ParseInt(kv[1], 10, 64)
    51  		if err != nil {
    52  			log.Fatalf("Cannot unmarshall %v from %s to int64: %v", kv[1], s, err)
    53  			return nil
    54  		}
    55  		switch key {
    56  		case "UNIX_TIME":
    57  			u.timestamp = value
    58  		case "CPU_pct":
    59  			u.CPUPct = value
    60  		case "MEM_RES_mbs":
    61  			u.MemResMbs = value
    62  		case "MEM_VIRT_mbs":
    63  			u.MemVirtMbs = value
    64  		case "MEM_pct":
    65  			u.MemPct = value
    66  		case "PEAK_NUM_ACTIONS":
    67  			u.PeakNumActions = value
    68  		}
    69  	}
    70  	return &u
    71  }
    72  
    73  func sortByTimestamp() {
    74  	sort.Slice(usages, func(i, j int) bool {
    75  		return usages[i].timestamp < usages[j].timestamp
    76  	})
    77  }
    78  
    79  func saveToCSV(logPath string) error {
    80  	filePath := logPath + ".csv"
    81  	f, err := os.Create(filePath)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	log.Infof("CSV file created at: %v", filePath)
    86  	defer f.Close()
    87  
    88  	w := csv.NewWriter(f)
    89  	defer w.Flush()
    90  
    91  	if err := w.Write([]string{
    92  		"timestamp",
    93  		"CPU_pct",
    94  		"MEM_RES_mbs",
    95  		"MEM_VIRT_mbs",
    96  		"MEM_pct",
    97  		"PEAK_NUM_ACTIONS",
    98  	}); err != nil {
    99  		return err
   100  	}
   101  
   102  	for _, u := range usages {
   103  		if err := w.Write([]string{
   104  			strconv.FormatInt(u.timestamp, 10),
   105  			strconv.FormatInt(u.CPUPct, 10),
   106  			strconv.FormatInt(u.MemResMbs, 10),
   107  			strconv.FormatInt(u.MemVirtMbs, 10),
   108  			strconv.FormatInt(u.MemPct, 10),
   109  			strconv.FormatInt(u.PeakNumActions, 10),
   110  		}); err != nil {
   111  			return err
   112  		}
   113  	}
   114  	return nil
   115  }
   116  
   117  func parseLogFileToSlice(file io.Reader) {
   118  	scanner := bufio.NewScanner(file)
   119  	for scanner.Scan() {
   120  		l := scanner.Text()
   121  		// Extract data from "Resource Usage: map[CPU_pct:0 MEM_RES_mbs:1246 MEM_VIRT_mbs:5895 MEM_pct:0 PEAK_NUM_ACTIONS:0 UNIX_TIME:1697736889]".
   122  		dataMatches := usageRegex.FindStringSubmatch(l)
   123  		if dataMatches == nil || len(dataMatches) != 2 {
   124  			continue
   125  		}
   126  		if usg := unmarshall(dataMatches[1]); usg != nil {
   127  			usages = append(usages, usg)
   128  		}
   129  	}
   130  }
   131  
   132  // Usage2CSV reads a reproxy.INFO file at logPath, and parse the resource usage
   133  // content as a CSV file. The CSV file will have the same name as the logPath
   134  // but append with a ".csv" suffix.
   135  func Usage2CSV(logPath string) error {
   136  	file, err := os.Open(logPath)
   137  	if err != nil {
   138  		log.Fatalf("Cannot open reproxy.INFO file at %v : %v", logPath, err)
   139  	}
   140  	defer file.Close()
   141  	parseLogFileToSlice(file)
   142  	sortByTimestamp()
   143  	return saveToCSV(logPath)
   144  }