Add profile command to REPL

For the time being, enabling profiling will disable tracing and vice versa. Once we add support for multiple tracers, this behavior can be changed.

Fixes #838 

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
Signed-off-by: repenno <rapenno@gmail.com>
This commit is contained in:
repenno
2018-11-06 08:33:27 -08:00
committed by Torin Sandall
parent 20831e9e69
commit 73e8e3a96e
4 changed files with 137 additions and 13 deletions
+2 -5
View File
@@ -62,9 +62,6 @@ const (
defaultPrettyLimit = 80
)
// default sorting order for profiler results
var defaultSortOrder = []string{"total_time_ns", "num_eval", "num_redo", "file", "line"}
func init() {
var params evalCommandParams
@@ -290,7 +287,7 @@ func eval(args []string, params evalCommandParams, w io.Writer) (int, error) {
}
if params.profile {
var sortOrder = defaultSortOrder
var sortOrder = pr.DefaultProfileSortOrder
if len(params.profileCriteria.v) != 0 {
sortOrder = getProfileSortOrder(strings.Split(params.profileCriteria.String(), ","))
@@ -328,7 +325,7 @@ func getProfileSortOrder(sortOrder []string) []string {
}
// compare the given sort order and the default
for _, cr := range defaultSortOrder {
for _, cr := range pr.DefaultProfileSortOrder {
if _, ok := sortOrderMap[cr]; !ok {
sortOrder = append(sortOrder, cr)
}
+3
View File
@@ -25,6 +25,9 @@ import (
"github.com/open-policy-agent/opa/topdown"
)
// DefaultProfileSortOrder is the default ordering unless something is specified in the CLI
var DefaultProfileSortOrder = []string{"total_time_ns", "num_eval", "num_redo", "file", "line"}
// DepAnalysisOutput contains the result of dependency analysis to be presented.
type DepAnalysisOutput struct {
Base []ast.Ref `json:"base,omitempty"`
+35 -5
View File
@@ -12,6 +12,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/open-policy-agent/opa/profiler"
"html/template"
"io"
"os"
@@ -41,6 +42,7 @@ type REPL struct {
buffer []string
txn storage.Transaction
metrics metrics.Metrics
profiler bool
// TODO(tsandall): replace this state with rule definitions
// inside the default module.
@@ -259,6 +261,8 @@ func (r *REPL) OneShot(ctx context.Context, line string) error {
return r.cmdMetrics()
case "instrument":
return r.cmdInstrument()
case "profile":
return r.cmdProfile()
case "types":
return r.cmdTypes()
case "unknown":
@@ -426,6 +430,7 @@ func (r *REPL) cmdShow(args []string) error {
Trace: r.traceEnabled(),
Metrics: r.metricsEnabled(),
Instrument: r.instrument,
Profile: r.profilerEnabled(),
}
b, err := json.MarshalIndent(debug, "", "\t")
if err != nil {
@@ -443,6 +448,7 @@ type replDebug struct {
Trace bool `json:"trace"`
Metrics bool `json:"metrics"`
Instrument bool `json:"instrument"`
Profile bool `json:"profile"`
}
func (r *REPL) traceEnabled() bool {
@@ -457,6 +463,7 @@ func (r *REPL) cmdTrace() error {
r.explain = explainOff
} else {
r.explain = explainTrace
r.profiler = false
}
return nil
}
@@ -489,6 +496,21 @@ func (r *REPL) cmdInstrument() error {
return nil
}
func (r *REPL) profilerEnabled() bool {
return r.profiler
}
// This function cmdProfile will turn tracing (explain) off if profile is turned on
func (r *REPL) cmdProfile() error {
if r.profiler {
r.profiler = false
} else {
r.profiler = true
r.explain = explainOff
}
return nil
}
func (r *REPL) cmdTypes() error {
r.types = !r.types
return nil
@@ -824,9 +846,15 @@ func (r *REPL) evalStatement(ctx context.Context, stmt interface{}) error {
func (r *REPL) evalBody(ctx context.Context, compiler *ast.Compiler, input ast.Value, body ast.Body) error {
var buf *topdown.BufferTracer
var bufProfiler *profiler.Profiler
var tracer topdown.Tracer
if r.explain != explainOff {
buf = topdown.NewBufferTracer()
tracer = buf
} else if r.profiler {
bufProfiler = profiler.New()
tracer = bufProfiler
}
eval := rego.New(
@@ -838,7 +866,7 @@ func (r *REPL) evalBody(ctx context.Context, compiler *ast.Compiler, input ast.V
rego.ParsedQuery(body),
rego.ParsedInput(input),
rego.Metrics(r.metrics),
rego.Tracer(buf),
rego.Tracer(tracer),
rego.Instrument(r.instrument),
rego.Runtime(r.runtime),
)
@@ -851,15 +879,16 @@ func (r *REPL) evalBody(ctx context.Context, compiler *ast.Compiler, input ast.V
Metrics: r.metrics,
}
if r.profiler {
output.Profile = bufProfiler.ReportTopNResults(-1, pr.DefaultProfileSortOrder)
}
output = output.WithLimit(r.prettyLimit)
if buf != nil {
if r.explain != explainOff {
mangleTrace(ctx, r.store, r.txn, *buf)
output.Explanation = *buf
}
// TODO(tsandall): add profiler output
switch r.outputFormat {
case "json":
return pr.JSON(r.output, output)
@@ -1044,9 +1073,10 @@ var builtin = [...]commandDesc{
{"json", []string{}, "set output format to JSON"},
{"pretty", []string{}, "set output format to pretty"},
{"pretty-limit", []string{}, "set pretty value output limit"},
{"trace", []string{}, "toggle full trace"},
{"trace", []string{}, "toggle full trace and turns off profiler"},
{"metrics", []string{}, "toggle metrics"},
{"instrument", []string{}, "toggle instrumentation"},
{"profile", []string{}, "toggle profiler and turns off trace"},
{"types", []string{}, "toggle type information"},
{"unknown", []string{"[ref-1 [ref-2 [...]]]"}, "toggle partial evaluation mode"},
{"dump", []string{"[path]"}, "dump raw data in storage"},
+97 -3
View File
@@ -323,7 +323,8 @@ func TestShowDebug(t *testing.T) {
expected := `{
"trace": false,
"metrics": false,
"instrument": false
"instrument": false,
"profile": false
}
`
assertREPLText(t, buffer, expected)
@@ -331,14 +332,29 @@ func TestShowDebug(t *testing.T) {
repl.OneShot(ctx, "trace")
repl.OneShot(ctx, "metrics")
repl.OneShot(ctx, "instrument")
repl.OneShot(ctx, "profile")
repl.OneShot(ctx, "show debug")
expected = `{
"trace": false,
"metrics": true,
"instrument": true,
"profile": true
}
`
assertREPLText(t, buffer, expected)
buffer.Reset()
repl.OneShot(ctx, "metrics")
repl.OneShot(ctx, "instrument")
repl.OneShot(ctx, "profile")
repl.OneShot(ctx, "trace")
repl.OneShot(ctx, "show debug")
expected = `{
"trace": true,
"metrics": true,
"instrument": true
"instrument": true,
"profile": false
}
`
assertREPLText(t, buffer, expected)
buffer.Reset()
}
@@ -1745,6 +1761,84 @@ func TestMetrics(t *testing.T) {
}
}
func TestProfile(t *testing.T) {
store := newTestStore()
ctx := context.Background()
txn := storage.NewTransactionOrDie(ctx, store, storage.WriteParams)
const numLines = 21
mod2 := []byte(`package rbac
input = {
"subject": "bob",
"resource": "foo123",
"action": "write",
}
bindings = [
{
"user": "alice",
"roles": ["dev", "test"],
},
{
"user": "bob",
"roles": ["test"],
},
]
roles = [
{
"name": "dev",
"permissions": [
{"resource": "foo123", "action": "write"},
{"resource": "foo123", "action": "read"},
],
},
{
"name": "test",
"permissions": [{"resource": "foo123", "action": "read"}],
},
]
default allow = false
allow {
user_has_role[role_name]
role_has_permission[role_name]
}
user_has_role[role_name] {
binding := bindings[_]
binding.user = input.subject
role_name := binding.roles[_]
}
role_has_permission[role_name] {
role := roles[_]
role_name := role.name
perm := role.permissions[_]
perm.resource = input.resource
perm.action = input.action
}`)
if err := store.UpsertPolicy(ctx, txn, "mod2", mod2); err != nil {
panic(err)
}
if err := store.Commit(ctx, txn); err != nil {
panic(err)
}
var buffer bytes.Buffer
repl := newRepl(store, &buffer)
repl.OneShot(ctx, "profile")
repl.OneShot(ctx, "data.rbac.allow")
result := buffer.String()
lines := strings.Split(result, "\n")
if len(lines) != numLines {
t.Fatal("Expected 21 lines, got :", len(lines))
}
buffer.Reset()
}
func TestInstrument(t *testing.T) {
ctx := context.Background()
store := newTestStore()