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 }