blob: fddc436372f7445135641844cb6baf0dde4df307 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package platform
import (
"context"
"os"
"path/filepath"
"time"
"chromiumos/tast/common/testexec"
upstartcommon "chromiumos/tast/common/upstart"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/platform/perfetto"
"chromiumos/tast/local/upstart"
"chromiumos/tast/testing"
)
const (
tracedJob = "traced"
tracedProbesJob = "traced_probes"
waitDuration = 10 * time.Second
// Trace data output in binary proto format.
traceOutputFile = "perfetto_trace.pb"
)
func init() {
testing.AddTest(&testing.Test{
Func: PerfettoSystemTracing,
Desc: "Verifies functions of Perfetto traced and traced_probes",
Contacts: []string{"chinglinyu@chromium.org", "chromeos-performance-eng@google.com"},
Data: []string{perfetto.TraceConfigFile},
Attr: []string{"group:mainline"},
})
}
// ensureJobsStopped stops traced and makes sure traced_probes is also stopped.
func ensureJobsStopped(ctx context.Context) error {
// Use a short context for waiting job status.
wctx, wcancel := context.WithTimeout(ctx, 10*time.Second)
defer wcancel()
// Stop traced.
if err := upstart.StopJob(wctx, tracedJob); err != nil {
return errors.Wrap(err, "failed to stop the traced job")
}
// Check that traced_probes is also stopped with traced.
if err := upstart.WaitForJobStatus(wctx, tracedProbesJob, upstartcommon.StopGoal, upstartcommon.WaitingState, upstart.RejectWrongGoal, waitDuration); err != nil {
return errors.Wrap(err, "the traced_probes job isn't stopped")
}
return nil
}
// collectTraceData collect a system-wide trace using the perfetto command line
// too.
func collectTraceData(ctx context.Context, s *testing.State) error {
// Trace config specifies a 5 sec duration. Use 20 sec to avoid premature timeout on slow devices.
wctx, wcancel := context.WithTimeout(ctx, 20*time.Second)
defer wcancel()
// Start a trace session using the perfetto command line tool.
traceOutputPath := filepath.Join(s.OutDir(), traceOutputFile)
traceConfigPath := s.DataPath(perfetto.TraceConfigFile)
// This runs a perfetto trace session with the options:
// -c traceConfigPath --txt: configure the trace session as defined in the text proto |traceConfigPath|
// -o traceOutputPath : save the trace data (binary proto) to |traceOutputPath|
cmd := testexec.CommandContext(wctx, "/usr/bin/perfetto", "-c", traceConfigPath, "--txt", "-o", traceOutputPath)
if err := cmd.Run(testexec.DumpLogOnError); err != nil {
return errors.Wrap(err, "failed to run the tracing session")
}
// Validate the trace data.
stat, err := os.Stat(traceOutputPath)
if err != nil {
return errors.Wrapf(err, "unexpected error stating %s", traceOutputPath)
}
s.Logf("Collected %d bytes of trace data", stat.Size())
// TODO(chinglinyu): really validate the trace data content.
return nil
}
// waitForRunning waits until |job| is up and running.
func waitForRunning(ctx context.Context, job string) error {
// Wait for the job to
return testing.Poll(ctx, func(context.Context) error {
return upstart.CheckJob(ctx, job)
}, &testing.PollOptions{
Timeout: 5 * time.Second,
Interval: 500 * time.Millisecond,
})
}
// getRunningJobStatus checks the status of job traced and traced_probes.
// Returns the traced and traced_probes process IDs on success or an error if
// either job is not in the running state, or either jobs has crashed (and
// remains in the zombie process status.
func getRunningJobStatus(ctx context.Context) (int, int, error) {
// Use a short context for waiting job status.
wctx, wcancel := context.WithTimeout(ctx, 15*time.Second)
defer wcancel()
// Ensure traced is not in the zombie process state.
if err := upstart.CheckJob(wctx, tracedJob); err != nil {
return 0, 0, err
}
// Get the PID of traced.
_, _, tracedPid, err := upstart.JobStatus(wctx, tracedJob)
if err != nil {
return 0, 0, err
}
// Wait for traced_probes, which starts on traced started.
if err := waitForRunning(wctx, tracedProbesJob); err != nil {
return 0, 0, err
}
// Get the PID of traced_probes.
_, _, tracedProbesPid, err := upstart.JobStatus(wctx, tracedProbesJob)
if err != nil {
return 0, 0, err
}
return tracedPid, tracedProbesPid, nil
}
// PerfettoSystemTracing tests perfetto system-wide trace collection.
func PerfettoSystemTracing(ctx context.Context, s *testing.State) {
wctx, wcancel := context.WithTimeout(ctx, 10*time.Second)
defer wcancel()
// Make sure traced is running (and start it if not).
if err := upstart.EnsureJobRunning(wctx, tracedJob); err != nil {
s.Fatalf("Job %s isn't running", tracedJob)
}
defer func() {
if err := ensureJobsStopped(ctx); err != nil {
s.Fatal("Error in stopping the jobs: ", err)
}
}()
// Remember the PID of both jobs to verify that the jobs didn't have seccomp crash during trace collection.
tracedPid, tracedProbesPid, err := getRunningJobStatus(ctx)
if err != nil {
s.Fatal("Error in getting job status: ", err)
}
if err := collectTraceData(ctx, s); err != nil {
s.Fatal("Failed to collect trace data: ", err)
}
tracedPid2, tracedProbesPid2, err := getRunningJobStatus(ctx)
if err != nil {
s.Fatal("Error in getting job status after trace collection: ", err)
}
// Check that PID stays the same as a heuristic that the jobs didn't crash during the test.
if tracedPid != tracedPid2 {
s.Errorf("Unexpected respawn of job %s (PID changed from %d to %d)", tracedJob, tracedPid, tracedPid2)
}
if tracedProbesPid != tracedProbesPid2 {
s.Errorf("Unexpected respawn of job %s (PID changed from %d to %d)", tracedProbesJob, tracedProbesPid, tracedProbesPid2)
}
}