mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-27 18:55:54 -06:00
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:
+2
-5
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user