github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/cmd/rpl2trace/rpl2trace.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 // Binary rpl2trace converts *.rpl into trace format. The json file can be used 16 // for chrome://tracing and https://ui.perfetto.dev 17 // 18 // $ rpl2trace --log_path /tmp/reproxy_log.rpl --output trace.json 19 // 20 // This binary also works with *.rrpl file. 21 // 22 // $ bazelisk run //cmd/rpl2trace --config=remotelinux -- \ 23 // --log_path /tmp/reproxy_log.rrpl --output /tmp/trace.json 24 package main 25 26 import ( 27 "context" 28 "encoding/json" 29 "flag" 30 "fmt" 31 "os" 32 "path/filepath" 33 "sort" 34 "strings" 35 "time" 36 37 lpb "github.com/bazelbuild/reclient/api/log" 38 "github.com/bazelbuild/reclient/internal/pkg/logger" 39 "github.com/bazelbuild/reclient/internal/pkg/rbeflag" 40 41 cpb "github.com/bazelbuild/remote-apis-sdks/go/api/command" 42 "github.com/bazelbuild/remote-apis-sdks/go/pkg/moreflag" 43 log "github.com/golang/glog" 44 ) 45 46 var ( 47 proxyLogDir []string 48 logPath = flag.String("log_path", "", "Path to reproxy_log.txt file. E.g., text:///tmp/reproxy_log.txt") 49 logFormat = flag.String("log_format", "text", "Format of proxy log. Currently only text is supported.") 50 outputFilename = flag.String("output", "trace.json", "output filename") 51 traceLevel = flag.Int("trace_level", 3, "trace level. 0=cmd req. 1=local only. 2=local+remote, 3=local+remote+worker") 52 ) 53 54 // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview 55 56 const ( 57 localTID = 1 58 remoteTID = 2 59 workerTID = 3 60 ) 61 62 type event struct { 63 Name string `json:"name"` 64 Cat string `json:"cat"` 65 Phase string `json:"ph"` 66 Timestamp int64 `json:"ts"` // micro seconds 67 Dur int64 `json:"dur"` // micro seconds 68 Pid int `json:"pid"` 69 Tid int `json:"tid"` 70 Args map[string]interface{} `json:"args"` 71 } 72 73 func convertEventTime(cat string, pid, tid int, ets map[string]*cpb.TimeInterval, args map[string]interface{}, level int) (events []event, start, end time.Time) { 74 name := args["target"] 75 for key, et := range ets { 76 // ignore events that doesn't have timestamp 77 // e.g. ServerWorkerExecution when RBE execution failed. 78 if et.From == nil || et.To == nil { 79 continue 80 } 81 from := et.GetFrom().AsTime() 82 to := et.GetTo().AsTime() 83 if start.IsZero() || start.After(from) { 84 start = from 85 } 86 if end.IsZero() || end.Before(to) { 87 end = to 88 } 89 dur := to.Sub(from) 90 if dur < 1*time.Microsecond { 91 continue 92 } 93 etid := tid 94 if strings.HasPrefix(key, "Server") { 95 etid++ 96 } 97 if etid <= level { 98 events = append(events, event{ 99 Name: fmt.Sprintf("%s:%s", cat, key), 100 Cat: key, 101 Phase: "X", 102 Timestamp: from.UnixNano() / 1e3, 103 Dur: int64(dur / time.Microsecond), 104 Pid: pid, 105 Tid: etid, 106 Args: args, 107 }) 108 log.Infof("%s - %s:%s %s %s %s", name, cat, key, from, to, dur) 109 } 110 } 111 dur := end.Sub(start) 112 if tid <= level { 113 events = append(events, event{ 114 Name: fmt.Sprintf("%s - %s", name, cat), 115 Cat: cat, 116 Phase: "X", 117 Timestamp: start.UnixNano() / 1e3, 118 Dur: int64(dur / time.Microsecond), 119 Pid: pid, 120 Tid: tid, 121 Args: args, 122 }) 123 } 124 log.Infof("%s - %s %s %s %s", name, cat, start, end, dur) 125 return events, start, end 126 } 127 128 func convertLogRecord(ctx context.Context, log *lpb.LogRecord, level int) ([]event, time.Time, time.Time) { 129 var events []event 130 pid := 0 131 var pfrom, pto time.Time 132 output := "???" 133 if outputs := log.GetCommand().GetOutput().GetOutputFiles(); len(outputs) > 0 { 134 output = outputs[0] 135 } 136 wd := log.GetCommand().GetWorkingDirectory() 137 output = strings.TrimPrefix(output, wd) 138 identifiers := log.GetCommand().GetIdentifiers() 139 rm := log.GetRemoteMetadata() 140 if rm != nil && level >= remoteTID { 141 evs, rfrom, rto := convertEventTime("Remote", pid, remoteTID, rm.GetEventTimes(), map[string]interface{}{ 142 "target": output, 143 "identifiers": identifiers, 144 "action_digest": rm.GetActionDigest(), 145 "result": rm.GetResult(), 146 }, level) 147 events = append(events, evs...) 148 pfrom = rfrom 149 pto = rto 150 } 151 lm := log.GetLocalMetadata() 152 if lm != nil { 153 evs, lfrom, lto := convertEventTime("Local", pid, localTID, lm.GetEventTimes(), map[string]interface{}{ 154 "target": output, 155 "identifiers": identifiers, 156 "result": lm.GetResult(), 157 }, level) 158 events = append(events, evs...) 159 if pfrom.IsZero() || pfrom.After(lfrom) { 160 pfrom = lfrom 161 } 162 if pto.IsZero() || pto.Before(lto) { 163 pto = lto 164 } 165 } 166 if level == 0 { 167 dur := pto.Sub(pfrom) 168 events = append(events, event{ 169 Name: output, 170 Cat: "Command Request", 171 Phase: "X", 172 Timestamp: pfrom.UnixNano() / 1e3, 173 Dur: int64(dur / time.Microsecond), 174 Pid: pid, 175 Tid: 0, 176 Args: map[string]interface{}{ 177 "target": output, 178 "identifiers": identifiers, 179 "result": log.GetResult(), 180 }, 181 }) 182 } 183 return events, pfrom, pto 184 } 185 186 func convertLogRecords(ctx context.Context, logs []*lpb.LogRecord, level int) []event { 187 var events []event 188 var running []time.Time 189 for _, log := range logs { 190 evs, from, to := convertLogRecord(ctx, log, level) 191 pid := 0 192 for i, r := range running { 193 if r.Before(from) { 194 running[i] = to 195 pid = i + 1 196 break 197 } 198 } 199 if pid == 0 { 200 running = append(running, to) 201 pid = len(running) 202 } 203 for i := range evs { 204 evs[i].Pid = pid 205 } 206 events = append(events, evs...) 207 } 208 sort.Slice(events, func(i, j int) bool { 209 if events[i].Timestamp != events[i].Timestamp { 210 return events[i].Timestamp < events[j].Timestamp 211 } 212 return events[i].Dur >= events[j].Dur 213 }) 214 return events 215 } 216 217 func convert(ctx context.Context, logs []*lpb.LogRecord, level int, fname string) ([]event, error) { 218 events := convertLogRecords(ctx, logs, level) 219 buf, err := json.Marshal(events) 220 if err != nil { 221 return events, err 222 } 223 return events, os.WriteFile(fname, buf, 0644) 224 } 225 226 func main() { 227 defer log.Flush() 228 ctx := context.Background() 229 flag.Var((*moreflag.StringListValue)(&proxyLogDir), "proxy_log_dir", "If provided, the directory path to a proxy log file of executed records.") 230 rbeflag.Parse() 231 var logRecords []*lpb.LogRecord 232 var err error 233 234 format, err := logger.ParseFormat(*logFormat) 235 if err != nil { 236 log.Fatal(err) 237 } 238 239 switch { 240 case *logPath != "": 241 fmt.Printf("Loading log from %v %v...\n", format, *logPath) 242 log.Infof("Loading log from %v %v...", format, *logPath) 243 logRecords, err = logger.ParseFromFile(format, *logPath) 244 if err != nil { 245 log.Fatalf("Failed reading proxy log: %v", err) 246 } 247 case len(proxyLogDir) > 0: 248 fmt.Printf("Loading log from %v %q...\n", format, proxyLogDir) 249 log.Infof("Loading log from %v %q...", format, proxyLogDir) 250 logRecords, _, err = logger.ParseFromLogDirs(format, proxyLogDir) 251 if err != nil { 252 log.Fatalf("Failed reading proxy log: %v", err) 253 } 254 default: 255 log.Fatal("Must provide proxy log path or proxy log dir.") 256 } 257 258 absOutput, err := filepath.Abs(*outputFilename) 259 if err != nil { 260 log.Fatalf("Abs path for %q: %v", *outputFilename, err) 261 } 262 log.Infof("Writing to %s...", absOutput) 263 events, err := convert(ctx, logRecords, *traceLevel, *outputFilename) 264 if err != nil { 265 log.Fatalf("Unable to convert into trace json: %v", err) 266 } 267 log.Infof("%d requests %d events", len(logRecords), len(events)) 268 fmt.Printf("%d requests %d events\n", len(logRecords), len(events)) 269 }