github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/tests/rkt-monitor/main.go (about) 1 // Copyright 2016 The rkt Authors 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 main 16 17 import ( 18 "bufio" 19 "encoding/csv" 20 "encoding/json" 21 "flag" 22 "fmt" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/appc/spec/schema" 31 "github.com/shirou/gopsutil/load" 32 "github.com/shirou/gopsutil/process" 33 "github.com/spf13/cobra" 34 ) 35 36 type ProcessStatus struct { 37 Pid int32 38 Name string // Name of process 39 CPU float64 // Percent of CPU used since last check 40 VMS uint64 // Virtual memory size 41 RSS uint64 // Resident set size 42 Swap uint64 // Swap size 43 ID string // Container ID 44 } 45 46 var ( 47 pidMap map[int32]*process.Process 48 49 flagVerbose bool 50 flagDuration string 51 flagShowOutput bool 52 flagSaveToCsv bool 53 flagCsvDir string 54 flagRepetitionNumber int 55 flagRktDir string 56 flagStage1Path string 57 58 cmdRktMonitor = &cobra.Command{ 59 Use: "rkt-monitor IMAGE", 60 Short: "Runs the specified ACI or pod manifest with rkt, and monitors rkt's usage", 61 Example: "rkt-monitor mem-stresser.aci -v -d 30s", 62 Run: runRktMonitor, 63 } 64 ) 65 66 func init() { 67 pidMap = make(map[int32]*process.Process) 68 69 cmdRktMonitor.Flags().BoolVarP(&flagVerbose, "verbose", "v", false, "Print current usage every second") 70 cmdRktMonitor.Flags().IntVarP(&flagRepetitionNumber, "repetitions", "r", 1, "Numbers of benchmark repetitions") 71 cmdRktMonitor.Flags().StringVarP(&flagDuration, "duration", "d", "10s", "How long to run the ACI") 72 cmdRktMonitor.Flags().BoolVarP(&flagShowOutput, "show-output", "o", false, "Display rkt's stdout and stderr") 73 cmdRktMonitor.Flags().BoolVarP(&flagSaveToCsv, "to-file", "f", false, "Save benchmark results to files in a temp dir") 74 cmdRktMonitor.Flags().StringVarP(&flagCsvDir, "output-dir", "w", "/tmp", "Specify directory to write results") 75 cmdRktMonitor.Flags().StringVarP(&flagRktDir, "rkt-dir", "p", "", "Directory with rkt binary") 76 cmdRktMonitor.Flags().StringVarP(&flagStage1Path, "stage1-path", "s", "", "Path to Stage1 image to use") 77 78 flag.Parse() 79 } 80 81 func main() { 82 cmdRktMonitor.Execute() 83 } 84 85 func runRktMonitor(cmd *cobra.Command, args []string) { 86 if len(args) != 1 { 87 cmd.Usage() 88 os.Exit(254) 89 } 90 91 d, err := time.ParseDuration(flagDuration) 92 if err != nil { 93 fmt.Printf("%v\n", err) 94 os.Exit(254) 95 } 96 97 if os.Getuid() != 0 { 98 fmt.Printf("need to be root to run rkt images\n") 99 os.Exit(254) 100 } 101 102 f, err := os.Open(args[0]) 103 if err != nil { 104 fmt.Printf("%v\n", err) 105 os.Exit(254) 106 } 107 decoder := json.NewDecoder(f) 108 109 podManifest := false 110 man := schema.PodManifest{} 111 err = decoder.Decode(&man) 112 if err == nil { 113 podManifest = true 114 } 115 116 var flavorType, testImage string 117 if flagStage1Path == "" { 118 flavorType = "stage1-coreos.aci" 119 } else { 120 if !fileExist(flagStage1Path) { 121 fmt.Fprintln(os.Stderr, "Given stage1 file path doesn't exist") 122 os.Exit(254) 123 } 124 _, flavorType = filepath.Split(flagStage1Path) 125 } 126 127 var containerId string 128 var stopCmd, execCmd, gcCmd *exec.Cmd 129 var loadAvg *load.AvgStat 130 var containerStarting, containerStarted, containerStopping, containerStopped time.Time 131 132 records := [][]string{{"Time", "PID name", "PID number", "RSS", "CPU"}} // csv headers 133 summaryRecords := [][]string{{"Load1", "Load5", "Load15", "StartTime", "StopTime"}} // csv summary headers 134 135 var rktBinary string 136 if flagRktDir != "" { 137 rktBinary = filepath.Join(flagRktDir, "rkt") 138 if !fileExist(rktBinary) { 139 fmt.Fprintln(os.Stderr, "rkt binary not found!") 140 os.Exit(1) 141 } 142 } else { 143 rktBinary = "rkt" 144 } 145 146 for i := 0; i < flagRepetitionNumber; i++ { 147 // build argument list for execCmd 148 argv := []string{"run", "--debug"} 149 150 if flagStage1Path != "" { 151 argv = append(argv, fmt.Sprintf("--stage1-path=%v", flagStage1Path)) 152 } 153 154 if podManifest { 155 argv = append(argv, "--pod-manifest", args[0]) 156 } else { 157 argv = append(argv, args[0], "--insecure-options=image") 158 } 159 argv = append(argv, "--net=default-restricted") 160 161 execCmd = exec.Command(rktBinary, argv...) 162 163 if flagShowOutput { 164 execCmd.Stderr = os.Stderr 165 } 166 167 cmdReader, err := execCmd.StdoutPipe() 168 if err != nil { 169 fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for execCmd", err) 170 os.Exit(254) 171 } 172 173 execCmdScanner := bufio.NewScanner(cmdReader) 174 175 startConfirmation := make(chan string, 1) 176 go func() { 177 var containerId string 178 for execCmdScanner.Scan() { 179 if flagShowOutput { 180 fmt.Println(execCmdScanner.Text()) 181 } 182 if strings.Contains(execCmdScanner.Text(), "APP-STARTED!") { 183 startConfirmation <- containerId 184 } else if strings.Contains(execCmdScanner.Text(), "Set hostname to") { 185 sl := strings.SplitAfter(execCmdScanner.Text(), "<rkt-") 186 containerId = sl[len(sl)-1] 187 containerId = containerId[:len(containerId)-2] 188 } 189 } 190 }() 191 containerStarting = time.Now() 192 err = execCmd.Start() 193 containerId = <-startConfirmation 194 containerStarted = time.Now() //here we are sure - container is running (issue: #3019) 195 close(startConfirmation) 196 197 if flagShowOutput { 198 execCmd.Stdout = os.Stdout 199 } 200 201 if err != nil { 202 fmt.Printf("%v\n", err) 203 os.Exit(254) 204 } 205 206 usages := make(map[int32][]*ProcessStatus) 207 208 timeToStop := time.Now().Add(d) 209 210 for time.Now().Before(timeToStop) { 211 usage, err := getUsage(int32(execCmd.Process.Pid)) 212 if err != nil { 213 panic(err) 214 } 215 if flagVerbose { 216 printUsage(usage) 217 } 218 219 if flagSaveToCsv { 220 records = addRecords(usage, records) 221 } 222 223 for _, ps := range usage { 224 usages[ps.Pid] = append(usages[ps.Pid], ps) 225 } 226 227 _, err = process.NewProcess(int32(execCmd.Process.Pid)) 228 if err != nil { 229 // process.Process.IsRunning is not implemented yet 230 fmt.Fprintf(os.Stderr, "rkt exited prematurely\n") 231 break 232 } 233 234 time.Sleep(time.Second) 235 } 236 237 loadAvg, err = load.Avg() 238 if err != nil { 239 fmt.Fprintf(os.Stderr, "measure load avg failed: %v\n", err) 240 } 241 242 stopCmd = exec.Command(rktBinary, "stop", containerId) 243 244 cmdStopReader, err := stopCmd.StdoutPipe() 245 if err != nil { 246 fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for stopCmd", err) 247 os.Exit(254) 248 } 249 cmdStopScanner := bufio.NewScanner(cmdStopReader) 250 251 containerStopping = time.Now() 252 stopConfirmation := make(chan bool, 1) 253 go func() { 254 for cmdStopScanner.Scan() { 255 if strings.Contains(cmdStopScanner.Text(), containerId) { 256 stopConfirmation <- true 257 return 258 } 259 } 260 stopConfirmation <- false 261 }() 262 err = stopCmd.Start() 263 if !<-stopConfirmation { 264 fmt.Println("WARNING: There was a problem stopping the container! (Container already stopped?)") 265 } 266 containerStopped = time.Now() 267 close(stopConfirmation) 268 269 if err != nil { 270 fmt.Printf("%v\n", err) 271 os.Exit(254) 272 } 273 274 gcCmd = exec.Command(rktBinary, "gc", "--grace-period=0") 275 gcCmd.Start() 276 277 for _, processHistory := range usages { 278 var avgCPU float64 279 var avgMem uint64 280 var peakMem uint64 281 282 for _, p := range processHistory { 283 avgCPU += p.CPU 284 avgMem += p.RSS 285 if peakMem < p.RSS { 286 peakMem = p.RSS 287 } 288 } 289 290 avgCPU = avgCPU / float64(len(processHistory)) 291 avgMem = avgMem / uint64(len(processHistory)) 292 293 if !flagSaveToCsv { 294 fmt.Printf("%s(%d): seconds alive: %d avg CPU: %f%% avg Mem: %s peak Mem: %s\n", processHistory[0].Name, processHistory[0].Pid, len(processHistory), avgCPU, formatSize(avgMem), formatSize(peakMem)) 295 } 296 } 297 298 if flagSaveToCsv { 299 summaryRecords = append(summaryRecords, []string{ 300 strconv.FormatFloat(loadAvg.Load1, 'g', 3, 64), 301 strconv.FormatFloat(loadAvg.Load5, 'g', 3, 64), 302 strconv.FormatFloat(loadAvg.Load15, 'g', 3, 64), 303 strconv.FormatFloat(float64(containerStarted.Sub(containerStarting).Nanoseconds())/float64(time.Millisecond), 'g', -1, 64), 304 strconv.FormatFloat(float64(containerStopped.Sub(containerStopping).Nanoseconds())/float64(time.Millisecond), 'g', -1, 64)}) 305 } 306 307 fmt.Printf("load average: Load1: %f Load5: %f Load15: %f\n", loadAvg.Load1, loadAvg.Load5, loadAvg.Load15) 308 fmt.Printf("container start time: %sms\n", strconv.FormatFloat(float64(containerStarted.Sub(containerStarting).Nanoseconds())/float64(time.Millisecond), 'g', -1, 64)) 309 fmt.Printf("container stop time: %sms\n", strconv.FormatFloat(float64(containerStopped.Sub(containerStopping).Nanoseconds())/float64(time.Millisecond), 'g', -1, 64)) 310 } 311 312 t := time.Now() 313 _, testImage = filepath.Split(args[0]) 314 prefix := fmt.Sprintf("%d-%02d-%02d_%02d-%02d_%s_%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), flavorType, testImage) 315 if flagSaveToCsv { 316 err = saveRecords(records, flagCsvDir, prefix+"_rkt_benchmark_interval.csv") 317 if err != nil { 318 fmt.Fprintf(os.Stderr, "Can't write to a file: %v\n", err) 319 } 320 err = saveRecords(summaryRecords, flagCsvDir, prefix+"_rkt_benchmark_summary.csv") 321 if err != nil { 322 fmt.Fprintf(os.Stderr, "Can't write to a summary file: %v\n", err) 323 } 324 } 325 } 326 327 func fileExist(filename string) bool { 328 if _, err := os.Stat(filename); err == nil { 329 return true 330 } 331 return false 332 } 333 334 func getUsage(pid int32) ([]*ProcessStatus, error) { 335 var statuses []*ProcessStatus 336 pids := []int32{pid} 337 for i := 0; i < len(pids); i++ { 338 proc, ok := pidMap[pids[i]] 339 if !ok { 340 var err error 341 proc, err = process.NewProcess(pids[i]) 342 if err != nil { 343 return nil, err 344 } 345 pidMap[pids[i]] = proc 346 } 347 s, err := getProcStatus(proc) 348 if err != nil { 349 return nil, err 350 } 351 statuses = append(statuses, s) 352 353 children, err := proc.Children() 354 if err != nil && err != process.ErrorNoChildren { 355 return nil, err 356 } 357 358 childloop: 359 for _, child := range children { 360 for _, p := range pids { 361 if p == child.Pid { 362 fmt.Printf("%d is in %#v\n", p, pids) 363 continue childloop 364 } 365 } 366 pids = append(pids, child.Pid) 367 } 368 } 369 return statuses, nil 370 } 371 372 func getProcStatus(p *process.Process) (*ProcessStatus, error) { 373 n, err := p.Name() 374 if err != nil { 375 return nil, err 376 } 377 c, err := p.Percent(0) 378 if err != nil { 379 return nil, err 380 } 381 m, err := p.MemoryInfo() 382 if err != nil { 383 return nil, err 384 } 385 return &ProcessStatus{ 386 Pid: p.Pid, 387 Name: n, 388 CPU: c, 389 VMS: m.VMS, 390 RSS: m.RSS, 391 Swap: m.Swap, 392 }, nil 393 } 394 395 func formatSize(size uint64) string { 396 if size > 1024*1024*1024 { 397 return strconv.FormatUint(size/(1024*1024*1024), 10) + " gB" 398 } 399 if size > 1024*1024 { 400 return strconv.FormatUint(size/(1024*1024), 10) + " mB" 401 } 402 if size > 1024 { 403 return strconv.FormatUint(size/1024, 10) + " kB" 404 } 405 return strconv.FormatUint(size, 10) + " B" 406 } 407 408 func printUsage(statuses []*ProcessStatus) { 409 for _, s := range statuses { 410 fmt.Printf("%s(%d): Mem: %s CPU: %f\n", s.Name, s.Pid, formatSize(s.RSS), s.CPU) 411 } 412 fmt.Printf("\n") 413 } 414 415 func addRecords(statuses []*ProcessStatus, records [][]string) [][]string { 416 for _, s := range statuses { 417 records = append(records, []string{time.Now().String(), s.Name, strconv.Itoa(int(s.Pid)), formatSize(s.RSS), strconv.FormatFloat(s.CPU, 'g', -1, 64)}) 418 } 419 return records 420 } 421 422 func saveRecords(records [][]string, dir, filename string) error { 423 csvFile, err := os.Create(filepath.Join(dir, filename)) 424 defer csvFile.Close() 425 if err != nil { 426 return err 427 } 428 429 w := csv.NewWriter(csvFile) 430 w.WriteAll(records) 431 if err := w.Error(); err != nil { 432 return err 433 } 434 435 return nil 436 }