Skip to content

Commit cc6b552

Browse files
committed
Add duration latencies to AccessRecord
Implements three levels of execution timing in nanoseconds: - BundleReference.duration: Time spent evaluating the Rego AST for each individual policy (excludes policy download/compilation time) - AccessRecord.Duration.phases: Total execution time per authorization phase (1=SYSTEM, 2=IDENTITY, 3=RESOURCE, 4=SCOPE) - AccessRecord.Duration.overall: Total Authorize() execution time, excluding the audit record transmission Uses time.Now()/time.Since() for lightweight nanosecond-granularity timing via Go monotonic clock. Signed-off-by: Greg Haskins <greg@manetu.com>
1 parent 6140b18 commit cc6b552

File tree

9 files changed

+176
-36
lines changed

9 files changed

+176
-36
lines changed

internal/core/phase.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import (
1414
* is the anded result from the individual phases
1515
*/
1616
type phase struct {
17-
bundles []*events.AccessRecord_BundleReference
17+
bundles []*events.AccessRecord_BundleReference
18+
duration uint64 // total phase duration in nanoseconds
1819
}
1920

2021
func (p *phase) append(r *events.AccessRecord_BundleReference) {

internal/core/phase1.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package core
66

77
import (
88
"context"
9+
"time"
910

1011
"github.com/manetu/policyengine/pkg/common"
1112
"github.com/manetu/policyengine/pkg/core/model"
@@ -46,10 +47,16 @@ func getPolicyForOperation(ctx context.Context, pe *PolicyEngine, mrn string) (*
4647
//
4748
// Value 0 is an leaves the result to be computed from the evaluation of other phases.
4849
func (p1 *phase1) exec(ctx context.Context, pe *PolicyEngine, input map[string]interface{}, op string) events.AccessRecord_Decision {
50+
phaseStart := time.Now()
51+
defer func() {
52+
p1.duration = uint64(time.Since(phaseStart).Nanoseconds())
53+
}()
54+
4955
var (
50-
result events.AccessRecord_Decision
51-
perr *common.PolicyError
52-
policy *model.Policy
56+
result events.AccessRecord_Decision
57+
perr *common.PolicyError
58+
policy *model.Policy
59+
evalDuration uint64
5360
)
5461

5562
result = events.AccessRecord_UNSPECIFIED
@@ -64,7 +71,10 @@ func (p1 *phase1) exec(ctx context.Context, pe *PolicyEngine, input map[string]i
6471
} else {
6572
logger.Debugf(agent, "authorize", "[phase1] got policy: %+v", policy)
6673

74+
evalStart := time.Now()
6775
p1.result, perr = policy.EvaluateInt(ctx, input)
76+
evalDuration = uint64(time.Since(evalStart).Nanoseconds())
77+
6878
if perr != nil {
6979
result = events.AccessRecord_DENY
7080
logger.Debugf(agent, "authorize", "[phase1] failed(err-%s)", perr)
@@ -84,7 +94,7 @@ func (p1 *phase1) exec(ctx context.Context, pe *PolicyEngine, input map[string]i
8494
}
8595
}
8696

87-
p1.append(buildBundleReference(perr, policy, events.AccessRecord_BundleReference_SYSTEM, op, bundleResult))
97+
p1.append(buildBundleReference(perr, policy, events.AccessRecord_BundleReference_SYSTEM, op, bundleResult, evalDuration))
8898

8999
return result
90100
}

internal/core/phase2.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"maps"
1010
"slices"
1111
"sync"
12+
"time"
1213

1314
"github.com/manetu/policyengine/pkg/common"
1415
"github.com/manetu/policyengine/pkg/core/model"
@@ -26,6 +27,11 @@ type phase2 struct {
2627
}
2728

2829
func (p2 *phase2) exec(ctx context.Context, pe *PolicyEngine, principalMap map[string]interface{}, input map[string]interface{}) bool {
30+
phaseStart := time.Now()
31+
defer func() {
32+
p2.duration = uint64(time.Since(phaseStart).Nanoseconds())
33+
}()
34+
2935
logger.Trace(agent, "authorize", "proceeding to phase2")
3036

3137
var policies []*model.Policy
@@ -52,7 +58,7 @@ func (p2 *phase2) exec(ctx context.Context, pe *PolicyEngine, principalMap map[s
5258
group, perr := pe.backend.GetGroup(ctx, groupMrn)
5359
if perr != nil {
5460
logger.Tracef(agent, "authorize", "[phase2] get rolebundle failed for group %s", groupMrn)
55-
p2.append(buildBundleReference(perr, nil, events.AccessRecord_BundleReference_IDENTITY, groupMrn, events.AccessRecord_DENY))
61+
p2.append(buildBundleReference(perr, nil, events.AccessRecord_BundleReference_IDENTITY, groupMrn, events.AccessRecord_DENY, 0))
5662
} else {
5763
for _, r := range group.Roles {
5864
roleMap[r] = struct{}{}
@@ -68,6 +74,7 @@ func (p2 *phase2) exec(ctx context.Context, pe *PolicyEngine, principalMap map[s
6874
policies = make([]*model.Policy, len(rs))
6975
decs := make([]bool, len(rs))
7076
errs := make([]*common.PolicyError, len(rs))
77+
durations := make([]uint64, len(rs))
7178

7279
// ------------ begin processing policies concurrently ---------------
7380
numRoles := len(rs)
@@ -88,7 +95,9 @@ func (p2 *phase2) exec(ctx context.Context, pe *PolicyEngine, principalMap map[s
8895
}
8996

9097
policies[i] = role.Policy
98+
evalStart := time.Now()
9199
decs[i], errs[i] = role.Policy.EvaluateBool(ctx, input)
100+
durations[i] = uint64(time.Since(evalStart).Nanoseconds())
92101
}(ind, roleMrn)
93102
}
94103

@@ -113,7 +122,7 @@ func (p2 *phase2) exec(ctx context.Context, pe *PolicyEngine, principalMap map[s
113122
desc = events.AccessRecord_GRANT
114123
}
115124

116-
p2.append(buildBundleReference(errs[i], policies[i], events.AccessRecord_BundleReference_IDENTITY, rs[i], desc))
125+
p2.append(buildBundleReference(errs[i], policies[i], events.AccessRecord_BundleReference_IDENTITY, rs[i], desc, durations[i]))
117126
}
118127

119128
return result

internal/core/phase3.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package core
66

77
import (
88
"context"
9+
"time"
910

1011
"github.com/manetu/policyengine/pkg/common"
1112
"github.com/manetu/policyengine/pkg/core/model"
@@ -22,10 +23,16 @@ type phase3 struct {
2223
// phase3 is executed only if prior resource resolution is successful. ie, either group is provided in PORC or resource
2324
// MRN is used to fully resolve the resource in "input"
2425
func (p3 *phase3) exec(ctx context.Context, pe *PolicyEngine, input map[string]interface{}) bool {
26+
phaseStart := time.Now()
27+
defer func() {
28+
p3.duration = uint64(time.Since(phaseStart).Nanoseconds())
29+
}()
30+
2531
var (
26-
result bool
27-
perr *common.PolicyError
28-
policy *model.Policy
32+
result bool
33+
perr *common.PolicyError
34+
policy *model.Policy
35+
evalDuration uint64
2936
)
3037

3138
// ResourceGroup policy check
@@ -37,7 +44,9 @@ func (p3 *phase3) exec(ctx context.Context, pe *PolicyEngine, input map[string]i
3744
logger.Debugf(agent, "authorize", "[phase3] error getting group for resource %s (err: %+v)", res.ID, perr)
3845
} else {
3946
policy = rg.Policy
47+
evalStart := time.Now()
4048
result, perr = rg.Policy.EvaluateBool(ctx, input)
49+
evalDuration = uint64(time.Since(evalStart).Nanoseconds())
4150
if perr != nil {
4251
logger.Debugf(agent, "authorize", "[phase3] phase3 failed(err-%s)", perr)
4352
}
@@ -47,7 +56,7 @@ func (p3 *phase3) exec(ctx context.Context, pe *PolicyEngine, input map[string]i
4756
if result {
4857
desc = events.AccessRecord_GRANT
4958
}
50-
p3.append(buildBundleReference(perr, policy, events.AccessRecord_BundleReference_RESOURCE, res.Group, desc))
59+
p3.append(buildBundleReference(perr, policy, events.AccessRecord_BundleReference_RESOURCE, res.Group, desc, evalDuration))
5160

5261
return result
5362
}

internal/core/phase4.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package core
77
import (
88
"context"
99
"sync"
10+
"time"
1011

1112
"github.com/manetu/policyengine/pkg/common"
1213
"github.com/manetu/policyengine/pkg/core/model"
@@ -26,6 +27,11 @@ type phase4 struct {
2627
}
2728

2829
func (p4 *phase4) exec(ctx context.Context, pe *PolicyEngine, principalMap map[string]interface{}, input map[string]interface{}) bool {
30+
phaseStart := time.Now()
31+
defer func() {
32+
p4.duration = uint64(time.Since(phaseStart).Nanoseconds())
33+
}()
34+
2935
logger.Trace(agent, "authorize", "proceeding to phase4")
3036
scs := []string{}
3137
if ss, ok := principalMap[Scopes].([]interface{}); ok {
@@ -54,6 +60,7 @@ func (p4 *phase4) exec(ctx context.Context, pe *PolicyEngine, principalMap map[s
5460
policies := make([]*model.Policy, numScopes)
5561
decs := make([]bool, numScopes)
5662
errs := make([]*common.PolicyError, numScopes)
63+
durations := make([]uint64, numScopes)
5764

5865
// ------------ begin processing policies concurrently ---------------
5966
wg := sync.WaitGroup{}
@@ -72,7 +79,9 @@ func (p4 *phase4) exec(ctx context.Context, pe *PolicyEngine, principalMap map[s
7279
}
7380

7481
policies[i] = scope.Policy
82+
evalStart := time.Now()
7583
decs[i], errs[i] = scope.Policy.EvaluateBool(ctx, input)
84+
durations[i] = uint64(time.Since(evalStart).Nanoseconds())
7685
}(ind, s)
7786
}
7887

@@ -96,7 +105,7 @@ func (p4 *phase4) exec(ctx context.Context, pe *PolicyEngine, principalMap map[s
96105
desc = events.AccessRecord_GRANT
97106
}
98107

99-
p4.append(buildBundleReference(errs[i], policies[i], events.AccessRecord_BundleReference_SCOPE, scs[i], desc))
108+
p4.append(buildBundleReference(errs[i], policies[i], events.AccessRecord_BundleReference_SCOPE, scs[i], desc, durations[i]))
100109
}
101110

102111
return result

internal/core/policyengine.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ func (pe *PolicyEngine) resolveResource(ctx context.Context, mrn string) (*model
221221

222222
// Authorize is the main function that calls opa
223223
func (pe *PolicyEngine) Authorize(ctx context.Context, input types.PORC, authOptions *options.AuthzOptions) bool {
224+
overallStart := time.Now()
224225
logger.Debug(agent, "authorize", "Enter")
225226
defer logger.Debug(agent, "authorize", "Exit")
226227

@@ -306,8 +307,15 @@ func (pe *PolicyEngine) Authorize(ctx context.Context, input types.PORC, authOpt
306307
reason string
307308
}{}
308309

310+
// Initialize duration tracking
311+
ar.Duration = &events.AccessRecord_Duration{
312+
Phases: make(map[uint32]uint64),
313+
}
314+
309315
// -------------------------- NOTE: all returns audited -----------------
310316
defer func() {
317+
// Capture overall duration just before sending audit (excluding audit send time)
318+
ar.Duration.Overall = uint64(time.Since(overallStart).Nanoseconds())
311319
pe.auditDecision(authOptions, ar, resMrn, auditDecision.reason, input, false, auditDecision.phase1Result)
312320
}()
313321

@@ -317,7 +325,7 @@ func (pe *PolicyEngine) Authorize(ctx context.Context, input types.PORC, authOpt
317325

318326
perr := &common.PolicyError{ReasonCode: events.AccessRecord_BundleReference_INVALPARAM_ERROR, Reason: err.Error()}
319327

320-
ar.References = append(ar.References, buildBundleReference(perr, nil, events.AccessRecord_BundleReference_RESOURCE, resMrn, events.AccessRecord_DENY))
328+
ar.References = append(ar.References, buildBundleReference(perr, nil, events.AccessRecord_BundleReference_RESOURCE, resMrn, events.AccessRecord_DENY, 0))
321329
ar.Decision = events.AccessRecord_DENY
322330

323331
auditDecision.reason = "failed to marshal PORC"
@@ -378,6 +386,12 @@ func (pe *PolicyEngine) Authorize(ctx context.Context, input types.PORC, authOpt
378386

379387
phasesWg.Wait()
380388

389+
// Collect per-phase durations
390+
ar.Duration.Phases[uint32(events.AccessRecord_BundleReference_SYSTEM)] = p1.duration
391+
ar.Duration.Phases[uint32(events.AccessRecord_BundleReference_IDENTITY)] = p2.duration
392+
ar.Duration.Phases[uint32(events.AccessRecord_BundleReference_RESOURCE)] = p3.duration
393+
ar.Duration.Phases[uint32(events.AccessRecord_BundleReference_SCOPE)] = p4.duration
394+
381395
logger.Debug(agent, "authorize", "phases completed...begin evaulation")
382396

383397
// include execution records for audit and display purposes
@@ -417,7 +431,7 @@ func (pe *PolicyEngine) Authorize(ctx context.Context, input types.PORC, authOpt
417431

418432
auditDecision.reason = "error getting resource"
419433

420-
ar.References = append(ar.References, buildBundleReference(resErr, nil, events.AccessRecord_BundleReference_RESOURCE, resMrn, events.AccessRecord_DENY))
434+
ar.References = append(ar.References, buildBundleReference(resErr, nil, events.AccessRecord_BundleReference_RESOURCE, resMrn, events.AccessRecord_DENY, 0))
421435

422436
return false
423437
}

internal/core/utils.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func getUnsafeBuiltins() map[string]struct{} {
2323
return m
2424
}
2525

26-
func buildBundleReference(policyError *common.PolicyError, policy *model.Policy, phase events.AccessRecord_BundleReference_Phase, id string, result events.AccessRecord_Decision) *events.AccessRecord_BundleReference {
26+
func buildBundleReference(policyError *common.PolicyError, policy *model.Policy, phase events.AccessRecord_BundleReference_Phase, id string, result events.AccessRecord_Decision, duration uint64) *events.AccessRecord_BundleReference {
2727
var policies []*events.AccessRecord_PolicyReference
2828

2929
event := &events.AccessRecord_PolicyReference{}
@@ -37,6 +37,7 @@ func buildBundleReference(policyError *common.PolicyError, policy *model.Policy,
3737
Id: id,
3838
Policies: policies,
3939
Phase: phase,
40+
Duration: duration,
4041
}
4142

4243
// error trumps everything

0 commit comments

Comments
 (0)