@@ -1212,14 +1212,15 @@ func runPerf(myTarget target.Target, noRoot bool, processes []Process, cmd *exec
1212
1212
}
1213
1213
// must manually terminate perf in cgroup scope when a timeout is specified and/or need to refresh cgroups
1214
1214
startPerfTimestamp := time .Now ()
1215
- var timeout int
1215
+ var cgroupTimeout int
1216
1216
if flagScope == scopeCgroup && (flagDuration != 0 || len (flagCidList ) == 0 ) {
1217
1217
if flagDuration > 0 && flagDuration < flagRefresh {
1218
- timeout = flagDuration
1218
+ cgroupTimeout = flagDuration
1219
1219
} else {
1220
- timeout = flagRefresh
1220
+ cgroupTimeout = flagRefresh
1221
1221
}
1222
1222
}
1223
+ // Start a goroutine to wait for and then process perf output
1223
1224
// Use a timer to determine when we received an entire frame of events from perf
1224
1225
// The timer will expire when no lines (events) have been received from perf for more than 100ms. This
1225
1226
// works because perf writes the events to stderr in a burst every collection interval, e.g., 5 seconds.
@@ -1230,22 +1231,17 @@ func runPerf(myTarget target.Target, noRoot bool, processes []Process, cmd *exec
1230
1231
frameCount := 0
1231
1232
stopAnonymousFuncChannel := make (chan bool )
1232
1233
go func () {
1234
+ stop := false
1233
1235
for {
1234
1236
select {
1235
1237
case <- t1 .C : // waits for timer to expire
1236
1238
case <- stopAnonymousFuncChannel : // wait for signal to exit the goroutine
1237
- return
1239
+ stop = true // exit the loop
1238
1240
}
1239
- if len (outputLines ) != 0 {
1240
- if flagWriteEventsToFile {
1241
- if err = writeEventsToFile (outputDir + "/" + myTarget .GetName ()+ "_" + "events.json" , outputLines ); err != nil {
1242
- err = fmt .Errorf ("failed to write events to raw file: %v" , err )
1243
- slog .Error (err .Error ())
1244
- return
1245
- }
1246
- }
1241
+ if ! stop && len (outputLines ) != 0 {
1242
+ // process the events
1247
1243
var metricFrames []MetricFrame
1248
- if metricFrames , frameTimestamp , err = ProcessEvents (outputLines , eventGroupDefinitions , metricDefinitions , processes , frameTimestamp , metadata , outputDir ); err != nil {
1244
+ if metricFrames , frameTimestamp , err = ProcessEvents (outputLines , eventGroupDefinitions , metricDefinitions , processes , frameTimestamp , metadata ); err != nil {
1249
1245
slog .Warn (err .Error ())
1250
1246
outputLines = [][]byte {} // empty it
1251
1247
continue
@@ -1254,57 +1250,58 @@ func runPerf(myTarget target.Target, noRoot bool, processes []Process, cmd *exec
1254
1250
frameCount += 1
1255
1251
metricFrames [i ].FrameCount = frameCount
1256
1252
}
1253
+ // send the metrics frames out to be printed
1257
1254
frameChannel <- metricFrames
1258
- outputLines = [][]byte {} // empty it
1255
+ // write the events to a file
1256
+ if flagWriteEventsToFile {
1257
+ if err = writeEventsToFile (outputDir + "/" + myTarget .GetName ()+ "_" + "events.json" , outputLines ); err != nil {
1258
+ err = fmt .Errorf ("failed to write events to raw file: %v" , err )
1259
+ slog .Error (err .Error ())
1260
+ return
1261
+ }
1262
+ }
1263
+ // empty the outputLines
1264
+ outputLines = [][]byte {}
1259
1265
}
1260
- if timeout != 0 && int (time .Since (startPerfTimestamp ).Seconds ()) > timeout {
1261
- err = localCommand .Process .Signal (os .Interrupt )
1262
- if err != nil {
1263
- err = fmt .Errorf ("failed to terminate perf: %v" , err )
1264
- slog .Error (err .Error ())
1266
+ // for cgroup scope, terminate perf if timeout is reached
1267
+ if flagScope == scopeCgroup {
1268
+ if stop || (cgroupTimeout != 0 && int (time .Since (startPerfTimestamp ).Seconds ()) > cgroupTimeout ) {
1269
+ err = localCommand .Process .Signal (os .Interrupt )
1270
+ if err != nil {
1271
+ err = fmt .Errorf ("failed to terminate perf: %v" , err )
1272
+ slog .Error (err .Error ())
1273
+ }
1265
1274
}
1266
1275
}
1276
+ if stop {
1277
+ break
1278
+ }
1267
1279
}
1280
+ // signal that the goroutine is done
1281
+ stopAnonymousFuncChannel <- true
1268
1282
}()
1269
- // read perf output
1283
+ // receive perf output
1270
1284
done := false
1271
1285
for ! done {
1272
1286
select {
1273
- case err := <- scriptErrorChannel :
1287
+ case err := <- scriptErrorChannel : // if there is an error running perf, it comes here
1274
1288
if err != nil {
1275
1289
slog .Error ("error from perf" , slog .String ("error" , err .Error ()))
1276
1290
}
1277
- done = true
1278
- case exitCode := <- exitcodeChannel :
1291
+ done = true // exit the loop
1292
+ case exitCode := <- exitcodeChannel : // when perf exits, the exit code comes to this channel
1279
1293
slog .Debug ("perf exited" , slog .Int ("exit code" , exitCode ))
1280
- done = true
1281
- case line := <- stderrChannel :
1294
+ done = true // exit the loop
1295
+ case line := <- stderrChannel : // perf output comes in on this channel, one line at a time
1282
1296
t1 .Stop ()
1283
1297
t1 .Reset (100 * time .Millisecond ) // 100ms is somewhat arbitrary, but seems to work
1298
+ // accumulate the lines, they will be processed in the goroutine when the timer expires
1284
1299
outputLines = append (outputLines , []byte (line ))
1285
1300
}
1286
1301
}
1287
1302
t1 .Stop ()
1288
1303
// send signal to exit the goroutine
1289
- defer func () { stopAnonymousFuncChannel <- true }()
1290
- // process any remaining events
1291
- if len (outputLines ) != 0 {
1292
- if flagWriteEventsToFile {
1293
- if err = writeEventsToFile (outputDir + "/" + myTarget .GetName ()+ "_" + "events.json" , outputLines ); err != nil {
1294
- err = fmt .Errorf ("failed to write events to raw file: %v" , err )
1295
- slog .Error (err .Error ())
1296
- return
1297
- }
1298
- }
1299
- var metricFrames []MetricFrame
1300
- if metricFrames , frameTimestamp , err = ProcessEvents (outputLines , eventGroupDefinitions , metricDefinitions , processes , frameTimestamp , metadata , outputDir ); err != nil {
1301
- slog .Error (err .Error ())
1302
- return
1303
- }
1304
- for i := range metricFrames {
1305
- frameCount += 1
1306
- metricFrames [i ].FrameCount = frameCount
1307
- }
1308
- frameChannel <- metricFrames
1309
- }
1304
+ stopAnonymousFuncChannel <- true
1305
+ // wait for the goroutine to exit
1306
+ <- stopAnonymousFuncChannel
1310
1307
}
0 commit comments