blob: 9c055779cee57de423bff0bf7ce7897d0177a6ea [file] [log] [blame]
// Copyright 2014 The Fuchsia 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 ninjalog
import (
"encoding/json"
"io/ioutil"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"go.fuchsia.dev/fuchsia/tools/build/ninjago/compdb"
)
func TestTrace(t *testing.T) {
for _, tc := range []struct {
desc string
flow [][]Step
criticalPath []Step
want []Trace
}{
{
desc: "empty",
},
{
desc: "without critical path",
flow: [][]Step{
{
{
Start: 76 * time.Millisecond,
End: 187 * time.Millisecond,
Out: "resources/inspector/devtools_extension_api.js",
CmdHash: 0x75430546595be7c2,
},
{
Start: 187 * time.Millisecond,
End: 21304 * time.Millisecond,
Out: "obj/third_party/pdfium/core/src/fpdfdoc/fpdfdoc.doc_formfield.o",
CmdHash: 0x2ac7111aa1ae86af,
TotalFloat: 100 * time.Millisecond,
Command: &compdb.Command{
Command: "prebuilt/third_party/goma/linux-x64/gomacc some args and files",
},
},
},
{
{
Start: 78 * time.Millisecond,
End: 286 * time.Millisecond,
Out: "gen/angle/commit_id.py",
CmdHash: 0x4ede38e2c1617d8c,
},
{
Start: 287 * time.Millisecond,
End: 290 * time.Millisecond,
Out: "obj/third_party/angle/src/copy_scripts.actions_rules_copies.stamp",
CmdHash: 0xb211d373de72f455,
TotalFloat: 420 * time.Millisecond,
Command: &compdb.Command{
Command: "touch obj/third_party/angle/src/copy_scripts.actions_rules_copies.stamp",
},
},
},
{
{
Start: 79 * time.Millisecond,
End: 287 * time.Millisecond,
Out: "gen/angle/copy_compiler_dll.bat",
CmdHash: 0x9fb635ad5d2c1109,
},
},
{
{
Start: 80 * time.Millisecond,
End: 284 * time.Millisecond,
Out: "gen/autofill_regex_constants.cc",
CmdHash: 0xfa33c8d7ce1d8791,
},
},
{
{
Start: 141 * time.Millisecond,
End: 287 * time.Millisecond,
Out: "PepperFlash/manifest.json",
CmdHash: 0x324f0a0b77c37ef,
},
},
{
{
Start: 142 * time.Millisecond,
End: 288 * time.Millisecond,
Out: "PepperFlash/libpepflashplayer.so",
CmdHash: 0x1e2c2b7845a4d4fe,
},
},
},
want: []Trace{
{
Name: "resources/inspector/devtools_extension_api.js",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 76 * 1000,
DurationMicros: (187 - 76) * 1000,
ProcessID: 1,
ThreadID: 0,
Args: map[string]interface{}{"total float": "0s"},
},
{
Name: "gen/angle/commit_id.py",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 78 * 1000,
DurationMicros: (286 - 78) * 1000,
ProcessID: 1,
ThreadID: 1,
Args: map[string]interface{}{"total float": "0s"},
},
{
Name: "gen/angle/copy_compiler_dll.bat",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 79 * 1000,
DurationMicros: (287 - 79) * 1000,
ProcessID: 1,
ThreadID: 2,
Args: map[string]interface{}{"total float": "0s"},
},
{
Name: "gen/autofill_regex_constants.cc",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 80 * 1000,
DurationMicros: (284 - 80) * 1000,
ProcessID: 1,
ThreadID: 3,
Args: map[string]interface{}{"total float": "0s"},
},
{
Name: "PepperFlash/manifest.json",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 141 * 1000,
DurationMicros: (287 - 141) * 1000,
ProcessID: 1,
ThreadID: 4,
Args: map[string]interface{}{"total float": "0s"},
},
{
Name: "PepperFlash/libpepflashplayer.so",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 142 * 1000,
DurationMicros: (288 - 142) * 1000,
ProcessID: 1,
ThreadID: 5,
Args: map[string]interface{}{"total float": "0s"},
},
{
Name: "obj/third_party/pdfium/core/src/fpdfdoc/fpdfdoc.doc_formfield.o",
Category: "gomacc",
EventType: completeEvent,
TimestampMicros: 187 * 1000,
DurationMicros: (21304 - 187) * 1000,
ProcessID: 1,
ThreadID: 0,
Args: map[string]interface{}{
"command": "prebuilt/third_party/goma/linux-x64/gomacc some args and files",
"total float": "100ms",
},
},
{
Name: "obj/third_party/angle/src/copy_scripts.actions_rules_copies.stamp",
Category: "touch",
EventType: completeEvent,
TimestampMicros: 287 * 1000,
DurationMicros: (290 - 287) * 1000,
ProcessID: 1,
ThreadID: 1,
Args: map[string]interface{}{
"command": "touch obj/third_party/angle/src/copy_scripts.actions_rules_copies.stamp",
"total float": "420ms",
},
},
},
},
{
desc: "with critical path",
flow: [][]Step{
{
{
End: 5 * time.Microsecond,
Out: "a",
TotalFloat: 6789 * time.Microsecond,
},
},
{
{
Start: 1 * time.Microsecond,
End: 2 * time.Microsecond,
Out: "b",
TotalFloat: 9876 * time.Microsecond,
},
{
Start: 10 * time.Microsecond,
End: 20 * time.Microsecond,
Drag: 1234 * time.Microsecond,
Out: "critical_c",
OnCriticalPath: true,
},
},
{
{
Start: 3 * time.Microsecond,
End: 5 * time.Microsecond,
Out: "critical_a",
OnCriticalPath: true,
Drag: 4321 * time.Microsecond,
},
{
Start: 5 * time.Microsecond,
End: 10 * time.Microsecond,
Out: "critical_b",
OnCriticalPath: true,
Drag: 2345 * time.Microsecond,
},
},
},
want: []Trace{
{
Name: "a",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 0,
DurationMicros: 5,
ProcessID: 1,
ThreadID: 2,
Args: map[string]interface{}{"total float": "6.789ms"},
},
{
Name: "b",
Category: "unknown",
EventType: completeEvent,
TimestampMicros: 1,
DurationMicros: 1,
ProcessID: 1,
ThreadID: 0,
Args: map[string]interface{}{"total float": "9.876ms"},
},
{
Name: "critical_a",
Category: "unknown,critical_path",
EventType: completeEvent,
TimestampMicros: 3,
DurationMicros: 2,
ProcessID: 1,
ThreadID: 1,
Args: map[string]interface{}{
"drag": "4.321ms",
"total float": "0s",
},
},
{
// Flow event going out of critical_a.
Name: "critical_path",
Category: "critical_path",
EventType: flowEventStart,
TimestampMicros: 4,
ProcessID: 1,
ThreadID: 1,
ID: 1,
},
{
Name: "critical_b",
Category: "unknown,critical_path",
EventType: completeEvent,
TimestampMicros: 5,
DurationMicros: 5,
ProcessID: 1,
ThreadID: 1,
Args: map[string]interface{}{
"drag": "2.345ms",
"total float": "0s",
},
},
{
// Flow event going out of critical_b.
Name: "critical_path",
Category: "critical_path",
EventType: flowEventStart,
TimestampMicros: 7,
ProcessID: 1,
ThreadID: 1,
ID: 2,
},
{
// Flow event pointing to critical_b.
Name: "critical_path",
Category: "critical_path",
EventType: flowEventEnd,
TimestampMicros: 7,
ProcessID: 1,
ThreadID: 1,
ID: 1,
BindingPoint: "e",
},
{
Name: "critical_c",
Category: "unknown,critical_path",
EventType: completeEvent,
TimestampMicros: 10,
DurationMicros: 10,
ProcessID: 1,
ThreadID: 0,
Args: map[string]interface{}{
"drag": "1.234ms",
"total float": "0s",
},
},
{
// Flow event pointing to critical_c.
Name: "critical_path",
Category: "critical_path",
EventType: flowEventEnd,
TimestampMicros: 15,
ProcessID: 1,
ThreadID: 0,
ID: 2,
BindingPoint: "e",
},
},
},
} {
t.Run(tc.desc, func(t *testing.T) {
got := ToTraces(tc.flow, 1)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("ToTrace()=%#v\nwant=%#v\ndiff (-want +got):\n%s", got, tc.want, diff)
}
})
}
}
func TestClangTracesToInterleave(t *testing.T) {
for _, tc := range []struct {
desc string
traces []Trace
granularity time.Duration
clangTraces map[string]clangTrace
want []Trace
wantErr bool
}{
{
desc: "empty",
},
{
desc: "successfully interleave",
traces: []Trace{
{TimestampMicros: 42, DurationMicros: 1000, Name: "output.cc.o", ProcessID: 123, ThreadID: 321},
},
granularity: 100 * time.Microsecond,
clangTraces: map[string]clangTrace{
"output.cc.json": {
TraceEvents: []Trace{
{TimestampMicros: 0, DurationMicros: 1000, Name: "ExecuteCompiler", EventType: completeEvent, ProcessID: 789, ThreadID: 1},
{TimestampMicros: 0, DurationMicros: 100, Name: "Source", EventType: completeEvent, ProcessID: 789, ThreadID: 2},
{TimestampMicros: 100, DurationMicros: 120, Name: "Source", EventType: completeEvent, ProcessID: 789, ThreadID: 2},
{TimestampMicros: 300, DurationMicros: 420, Name: "Frontend", EventType: completeEvent, ProcessID: 789, ThreadID: 3},
// Events below should be filtered.
{TimestampMicros: 0, DurationMicros: 800, Name: "NotComplete", ProcessID: 789, ThreadID: 4},
{TimestampMicros: 0, DurationMicros: 10, Name: "TooShort", EventType: completeEvent, ProcessID: 789, ThreadID: 5},
},
},
},
want: []Trace{
{TimestampMicros: 42, DurationMicros: 1000, Name: "ExecuteCompiler", EventType: completeEvent, ProcessID: 123, ThreadID: 321},
{TimestampMicros: 42, DurationMicros: 100, Name: "Source", EventType: completeEvent, ProcessID: 123, ThreadID: 321},
{TimestampMicros: 142, DurationMicros: 120, Name: "Source", EventType: completeEvent, ProcessID: 123, ThreadID: 321},
{TimestampMicros: 342, DurationMicros: 420, Name: "Frontend", EventType: completeEvent, ProcessID: 123, ThreadID: 321},
},
},
{
desc: "outputs missing clang traces are skipped",
traces: []Trace{
{TimestampMicros: 42, DurationMicros: 1000, Name: "output.cc.o", ProcessID: 123, ThreadID: 321},
{TimestampMicros: 100, DurationMicros: 2000, Name: "missing_trace.cc.o", ProcessID: 234, ThreadID: 432},
{TimestampMicros: 200, DurationMicros: 900, Name: "another_output.cc.o", ProcessID: 345, ThreadID: 543},
},
granularity: 100 * time.Microsecond,
clangTraces: map[string]clangTrace{
"output.cc.json": {
TraceEvents: []Trace{
{TimestampMicros: 0, DurationMicros: 1000, Name: "ExecuteCompiler", EventType: completeEvent, ProcessID: 789, ThreadID: 1},
},
},
"another_output.cc.json": {
TraceEvents: []Trace{
{TimestampMicros: 100, DurationMicros: 200, Name: "ExecuteCompiler", EventType: completeEvent, ProcessID: 789, ThreadID: 1},
},
},
},
want: []Trace{
{TimestampMicros: 42, DurationMicros: 1000, Name: "ExecuteCompiler", EventType: completeEvent, ProcessID: 123, ThreadID: 321},
{TimestampMicros: 300, DurationMicros: 200, Name: "ExecuteCompiler", EventType: completeEvent, ProcessID: 345, ThreadID: 543},
},
},
{
desc: "sub event longer than main step",
traces: []Trace{
{TimestampMicros: 42, DurationMicros: 1000, Name: "output.cc.o", ProcessID: 123, ThreadID: 321},
},
granularity: 100 * time.Microsecond,
clangTraces: map[string]clangTrace{
"output.cc.json": {
TraceEvents: []Trace{
{TimestampMicros: 0, DurationMicros: 999999, Name: "ExecuteCompiler", EventType: completeEvent, ProcessID: 789, ThreadID: 1},
},
},
},
wantErr: true,
},
} {
t.Run(tc.desc, func(t *testing.T) {
tmpDir := t.TempDir()
for filename, trace := range tc.clangTraces {
writeJson(t, filepath.Join(tmpDir, filename), trace)
}
got, err := ClangTracesToInterleave(tc.traces, tmpDir, tc.granularity)
if (err != nil) != tc.wantErr {
t.Errorf("ClangTracesToInterleave got error: %v, want error: %t", err, tc.wantErr)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("ClangTracesToInterleave got: %#v, want: %#v, diff (-want, +got):\n%s", got, tc.want, diff)
}
})
}
}
// writeJson writes data as json into file named p.
func writeJson(t *testing.T, p string, data interface{}) {
raw, err := json.Marshal(data)
if err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(p, raw, 0o600); err != nil {
t.Fatal(err)
}
}