Skip to content

Commit ac3ba04

Browse files
authored
adds a couple of benchmarks for grant expansion (#594)
1 parent 60ab7a9 commit ac3ba04

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed

pkg/dotc1z/sync_runs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,12 @@ func (c *C1File) StartOrResumeSync(ctx context.Context, syncType connectorstore.
472472
return c.currentSyncID, true, nil
473473
}
474474

475+
// SetSyncID sets the current sync ID. This is only intended for testing.
476+
func (c *C1File) SetSyncID(_ context.Context, syncID string) error {
477+
c.currentSyncID = syncID
478+
return nil
479+
}
480+
475481
func (c *C1File) StartNewSync(ctx context.Context, syncType connectorstore.SyncType, parentSyncID string) (string, error) {
476482
ctx, span := tracer.Start(ctx, "C1File.StartNewSync")
477483
defer span.End()
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package expand
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"maps"
7+
"os"
8+
"path/filepath"
9+
"runtime"
10+
"slices"
11+
"testing"
12+
13+
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
14+
reader_v2 "github.com/conductorone/baton-sdk/pb/c1/reader/v2"
15+
"github.com/conductorone/baton-sdk/pkg/annotations"
16+
"github.com/conductorone/baton-sdk/pkg/dotc1z"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
// ~50s.
21+
func BenchmarkExpandSmall(b *testing.B) {
22+
benchmarkExpand(b, "36zGvJw3uxU1QMJKU2yPVQ1hBOC")
23+
}
24+
25+
// ~70s.
26+
func BenchmarkExpandSmallMedium(b *testing.B) {
27+
benchmarkExpand(b, "36zM46KKuaBq0wjSSvKh5o0350y")
28+
}
29+
30+
func getTestdataPath(syncID string) string {
31+
_, filename, _, _ := runtime.Caller(0)
32+
return filepath.Join(filepath.Dir(filename), "testdata", fmt.Sprintf("sync.%s.unexpanded", syncID))
33+
}
34+
35+
// copyGraph creates a deep copy of an EntitlementGraph.
36+
func copyGraph(g *EntitlementGraph) *EntitlementGraph {
37+
newGraph := &EntitlementGraph{
38+
NextNodeID: g.NextNodeID,
39+
NextEdgeID: g.NextEdgeID,
40+
Nodes: make(map[int]Node, len(g.Nodes)),
41+
EntitlementsToNodes: make(map[string]int, len(g.EntitlementsToNodes)),
42+
SourcesToDestinations: make(map[int]map[int]int, len(g.SourcesToDestinations)),
43+
DestinationsToSources: make(map[int]map[int]int, len(g.DestinationsToSources)),
44+
Edges: make(map[int]Edge, len(g.Edges)),
45+
Loaded: g.Loaded,
46+
Depth: g.Depth,
47+
Actions: make([]*EntitlementGraphAction, len(g.Actions)),
48+
HasNoCycles: g.HasNoCycles,
49+
}
50+
51+
for k, v := range g.Nodes {
52+
newGraph.Nodes[k] = Node{Id: v.Id, EntitlementIDs: slices.Clone(v.EntitlementIDs)}
53+
}
54+
55+
maps.Copy(newGraph.EntitlementsToNodes, g.EntitlementsToNodes)
56+
57+
for k, v := range g.SourcesToDestinations {
58+
newGraph.SourcesToDestinations[k] = maps.Clone(v)
59+
}
60+
61+
for k, v := range g.DestinationsToSources {
62+
newGraph.DestinationsToSources[k] = maps.Clone(v)
63+
}
64+
65+
for k, v := range g.Edges {
66+
newGraph.Edges[k] = Edge{
67+
EdgeID: v.EdgeID,
68+
SourceID: v.SourceID,
69+
DestinationID: v.DestinationID,
70+
IsExpanded: v.IsExpanded,
71+
IsShallow: v.IsShallow,
72+
ResourceTypeIDs: slices.Clone(v.ResourceTypeIDs),
73+
}
74+
}
75+
76+
for i, action := range g.Actions {
77+
newGraph.Actions[i] = &EntitlementGraphAction{
78+
SourceEntitlementID: action.SourceEntitlementID,
79+
DescendantEntitlementID: action.DescendantEntitlementID,
80+
Shallow: action.Shallow,
81+
ResourceTypeIDs: slices.Clone(action.ResourceTypeIDs),
82+
PageToken: action.PageToken,
83+
}
84+
}
85+
86+
return newGraph
87+
}
88+
89+
// loadEntitlementGraphFromC1Z builds the entitlement graph by scanning all grants
90+
// and looking for GrantExpandable annotations.
91+
func loadEntitlementGraphFromC1Z(ctx context.Context, c1f *dotc1z.C1File) (*EntitlementGraph, error) {
92+
graph := NewEntitlementGraph(ctx)
93+
94+
pageToken := ""
95+
for {
96+
resp, err := c1f.ListGrants(ctx, v2.GrantsServiceListGrantsRequest_builder{PageToken: pageToken}.Build())
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
for _, grant := range resp.GetList() {
102+
annos := annotations.Annotations(grant.GetAnnotations())
103+
expandable := &v2.GrantExpandable{}
104+
_, err := annos.Pick(expandable)
105+
if err != nil {
106+
return nil, err
107+
}
108+
if len(expandable.GetEntitlementIds()) == 0 {
109+
continue
110+
}
111+
112+
for _, srcEntitlementID := range expandable.GetEntitlementIds() {
113+
srcEntitlement, err := c1f.GetEntitlement(ctx, reader_v2.EntitlementsReaderServiceGetEntitlementRequest_builder{
114+
EntitlementId: srcEntitlementID,
115+
}.Build())
116+
if err != nil {
117+
continue // Skip if source entitlement not found
118+
}
119+
120+
graph.AddEntitlement(grant.GetEntitlement())
121+
graph.AddEntitlement(srcEntitlement.GetEntitlement())
122+
_ = graph.AddEdge(ctx,
123+
srcEntitlement.GetEntitlement().GetId(),
124+
grant.GetEntitlement().GetId(),
125+
expandable.GetShallow(),
126+
expandable.GetResourceTypeIds(),
127+
)
128+
}
129+
}
130+
131+
pageToken = resp.GetNextPageToken()
132+
if pageToken == "" {
133+
break
134+
}
135+
}
136+
137+
graph.Loaded = true
138+
return graph, nil
139+
}
140+
141+
func benchmarkExpand(b *testing.B, syncID string) {
142+
c1zPath := getTestdataPath(syncID)
143+
if _, err := os.Stat(c1zPath); os.IsNotExist(err) {
144+
b.Skipf("testdata file not found: %s", c1zPath)
145+
}
146+
147+
ctx := context.Background()
148+
149+
// Open the c1z file once to get stats
150+
c1f, err := dotc1z.NewC1ZFile(ctx, c1zPath)
151+
require.NoError(b, err)
152+
defer c1f.Close()
153+
154+
// Load the graph
155+
graph, err := loadEntitlementGraphFromC1Z(ctx, c1f)
156+
require.NoError(b, err)
157+
158+
b.Logf("Graph loaded: %d nodes, %d edges", len(graph.Nodes), len(graph.Edges))
159+
160+
b.ResetTimer()
161+
162+
for i := 0; i < b.N; i++ {
163+
// Func is for defers to be human undertandable.
164+
func(i int) {
165+
// Make a copy of the graph for each iteration
166+
graphCopy := copyGraph(graph)
167+
168+
// Create a fresh c1z for each iteration (copy original)
169+
tmpFile, err := os.CreateTemp("", "bench-expand-*.c1z")
170+
require.NoError(b, err)
171+
tmpPath := tmpFile.Name()
172+
tmpFile.Close()
173+
defer os.Remove(tmpPath)
174+
175+
// Copy original c1z to temp
176+
srcData, err := os.ReadFile(c1zPath)
177+
require.NoError(b, err)
178+
err = os.WriteFile(tmpPath, srcData, 0600)
179+
require.NoError(b, err)
180+
181+
c1fCopy, err := dotc1z.NewC1ZFile(ctx, tmpPath)
182+
require.NoError(b, err)
183+
defer c1fCopy.Close()
184+
185+
err = c1fCopy.SetSyncID(ctx, syncID)
186+
require.NoError(b, err)
187+
188+
expander := NewExpander(c1fCopy, graphCopy)
189+
190+
// ---------------------------------------
191+
192+
b.StartTimer()
193+
err = expander.Run(ctx)
194+
b.StopTimer()
195+
196+
// ---------------------------------------
197+
require.NoError(b, err)
198+
}(i)
199+
}
200+
}
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)