Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6c62136

Browse files
authored
feat: add a dependency management graph for agents (#20208)
Relates tocoder/internal#1093This is the first of N pull requests to allow coder script ordering.It introduces what is for now dead code, but paves the way for variousinterfaces that allow coder scripts and other processes to depend on oneanother via CLI commands and terraform configurations.The next step is to add reactivity to the graph, such that changes inthe status of one vertex will propagate and allow other vertices tochange their own statuses.Concurrency and stress testing yield the following:CPU Profile:<img width="1512" height="862" alt="Screenshot 2025-10-17 at 10 38 52"src="https://github.com/user-attachments/assets/f46cf1a2-a0b2-4c02-81a0-069798108ee5"/>Mem Profile:<img width="1512" height="862" alt="Screenshot 2025-10-17 at 10 38 01"src="https://github.com/user-attachments/assets/45be1235-fff6-45ba-a50d-db9880377bd0"/>Predictably, lock contention and memory allocation are the largestcomponents of this system under stress. Nothing seems untoward.
1 parent51d3abb commit6c62136

File tree

9 files changed

+671
-0
lines changed

9 files changed

+671
-0
lines changed

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ node_modules/
1212
vendor/
1313
yarn-error.log
1414

15+
# Test output files
16+
test-output/
17+
1518
# VSCode settings.
1619
**/.vscode/*
1720
# Allow VSCode recommendations and default settings in project root.

‎Makefile‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ gen/db: $(DB_GEN_FILES)
676676
.PHONY: gen/db
677677

678678
gen/golden-files:\
679+
agent/unit/testdata/.gen-golden\
679680
cli/testdata/.gen-golden\
680681
coderd/.gen-golden\
681682
coderd/notifications/.gen-golden\
@@ -952,6 +953,10 @@ clean/golden-files:
952953
-type f -name'*.golden' -delete
953954
.PHONY: clean/golden-files
954955

956+
agent/unit/testdata/.gen-golden:$(wildcard agent/unit/testdata/*.golden)$(GO_SRC_FILES)$(wildcard agent/unit/*_test.go)
957+
TZ=UTC gotest ./agent/unit -run="TestGraph" -update
958+
touch"$@"
959+
955960
cli/testdata/.gen-golden:$(wildcard cli/testdata/*.golden)$(wildcard cli/*.tpl)$(GO_SRC_FILES)$(wildcard cli/*_test.go)
956961
TZ=UTC gotest ./cli -run="Test(CommandHelp|ServerYAML|ErrorExamples|.*Golden)" -update
957962
touch"$@"

‎agent/unit/graph.go‎

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package unit
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
"golang.org/x/xerrors"
8+
"gonum.org/v1/gonum/graph/encoding/dot"
9+
"gonum.org/v1/gonum/graph/simple"
10+
"gonum.org/v1/gonum/graph/topo"
11+
)
12+
13+
// Graph provides a bidirectional interface over gonum's directed graph implementation.
14+
// While the underlying gonum graph is directed, we overlay bidirectional semantics
15+
// by distinguishing between forward and reverse edges. Wanting and being wanted by
16+
// other units are related but different concepts that have different graph traversal
17+
// implications when Units update their status.
18+
//
19+
// The graph stores edge types to represent different relationships between units,
20+
// allowing for domain-specific semantics beyond simple connectivity.
21+
typeGraph[EdgeType,VertexTypecomparable]struct {
22+
mu sync.RWMutex
23+
// The underlying gonum graph. It stores vertices and edges without knowing about the types of the vertices and edges.
24+
gonumGraph*simple.DirectedGraph
25+
// Maps vertices to their IDs so that a gonum vertex ID can be used to lookup the vertex type.
26+
vertexToIDmap[VertexType]int64
27+
// Maps vertex IDs to their types so that a vertex type can be used to lookup the gonum vertex ID.
28+
idToVertexmap[int64]VertexType
29+
// The next ID to assign to a vertex.
30+
nextIDint64
31+
// Store edge types by "fromID->toID" key. This is used to lookup the edge type for a given edge.
32+
edgeTypesmap[string]EdgeType
33+
}
34+
35+
// Edge is a convenience type for representing an edge in the graph.
36+
// It encapsulates the from and to vertices and the edge type itself.
37+
typeEdge[EdgeType,VertexTypecomparable]struct {
38+
FromVertexType
39+
ToVertexType
40+
EdgeEdgeType
41+
}
42+
43+
// AddEdge adds an edge to the graph. It initializes the graph and metadata on first use,
44+
// checks for cycles, and adds the edge to the gonum graph.
45+
func (g*Graph[EdgeType,VertexType])AddEdge(from,toVertexType,edgeEdgeType)error {
46+
g.mu.Lock()
47+
deferg.mu.Unlock()
48+
49+
ifg.gonumGraph==nil {
50+
g.gonumGraph=simple.NewDirectedGraph()
51+
g.vertexToID=make(map[VertexType]int64)
52+
g.idToVertex=make(map[int64]VertexType)
53+
g.edgeTypes=make(map[string]EdgeType)
54+
g.nextID=1
55+
}
56+
57+
fromID:=g.getOrCreateVertexID(from)
58+
toID:=g.getOrCreateVertexID(to)
59+
60+
ifg.canReach(to,from) {
61+
returnxerrors.Errorf("adding edge (%v -> %v) would create a cycle",from,to)
62+
}
63+
64+
g.gonumGraph.SetEdge(simple.Edge{F:simple.Node(fromID),T:simple.Node(toID)})
65+
66+
edgeKey:=fmt.Sprintf("%d->%d",fromID,toID)
67+
g.edgeTypes[edgeKey]=edge
68+
69+
returnnil
70+
}
71+
72+
// GetForwardAdjacentVertices returns all the edges that originate from the given vertex.
73+
func (g*Graph[EdgeType,VertexType])GetForwardAdjacentVertices(fromVertexType) []Edge[EdgeType,VertexType] {
74+
g.mu.RLock()
75+
deferg.mu.RUnlock()
76+
77+
fromID,exists:=g.vertexToID[from]
78+
if!exists {
79+
return []Edge[EdgeType,VertexType]{}
80+
}
81+
82+
edges:= []Edge[EdgeType,VertexType]{}
83+
toNodes:=g.gonumGraph.From(fromID)
84+
fortoNodes.Next() {
85+
toID:=toNodes.Node().ID()
86+
to:=g.idToVertex[toID]
87+
88+
// Get the edge type
89+
edgeKey:=fmt.Sprintf("%d->%d",fromID,toID)
90+
edgeType:=g.edgeTypes[edgeKey]
91+
92+
edges=append(edges,Edge[EdgeType,VertexType]{From:from,To:to,Edge:edgeType})
93+
}
94+
95+
returnedges
96+
}
97+
98+
// GetReverseAdjacentVertices returns all the edges that terminate at the given vertex.
99+
func (g*Graph[EdgeType,VertexType])GetReverseAdjacentVertices(toVertexType) []Edge[EdgeType,VertexType] {
100+
g.mu.RLock()
101+
deferg.mu.RUnlock()
102+
103+
toID,exists:=g.vertexToID[to]
104+
if!exists {
105+
return []Edge[EdgeType,VertexType]{}
106+
}
107+
108+
edges:= []Edge[EdgeType,VertexType]{}
109+
fromNodes:=g.gonumGraph.To(toID)
110+
forfromNodes.Next() {
111+
fromID:=fromNodes.Node().ID()
112+
from:=g.idToVertex[fromID]
113+
114+
// Get the edge type
115+
edgeKey:=fmt.Sprintf("%d->%d",fromID,toID)
116+
edgeType:=g.edgeTypes[edgeKey]
117+
118+
edges=append(edges,Edge[EdgeType,VertexType]{From:from,To:to,Edge:edgeType})
119+
}
120+
121+
returnedges
122+
}
123+
124+
// getOrCreateVertexID returns the ID for a vertex, creating it if it doesn't exist.
125+
func (g*Graph[EdgeType,VertexType])getOrCreateVertexID(vertexVertexType)int64 {
126+
ifid,exists:=g.vertexToID[vertex];exists {
127+
returnid
128+
}
129+
130+
id:=g.nextID
131+
g.nextID++
132+
g.vertexToID[vertex]=id
133+
g.idToVertex[id]=vertex
134+
135+
// Add the node to the gonum graph
136+
g.gonumGraph.AddNode(simple.Node(id))
137+
138+
returnid
139+
}
140+
141+
// canReach checks if there is a path from the start vertex to the end vertex.
142+
func (g*Graph[EdgeType,VertexType])canReach(start,endVertexType)bool {
143+
ifstart==end {
144+
returntrue
145+
}
146+
147+
startID,startExists:=g.vertexToID[start]
148+
endID,endExists:=g.vertexToID[end]
149+
150+
if!startExists||!endExists {
151+
returnfalse
152+
}
153+
154+
// Use gonum's built-in path existence check
155+
returntopo.PathExistsIn(g.gonumGraph,simple.Node(startID),simple.Node(endID))
156+
}
157+
158+
// ToDOT exports the graph to DOT format for visualization
159+
func (g*Graph[EdgeType,VertexType])ToDOT(namestring) (string,error) {
160+
g.mu.RLock()
161+
deferg.mu.RUnlock()
162+
163+
ifg.gonumGraph==nil {
164+
return"",xerrors.New("graph is not initialized")
165+
}
166+
167+
// Marshal the graph to DOT format
168+
dotBytes,err:=dot.Marshal(g.gonumGraph,name,""," ")
169+
iferr!=nil {
170+
return"",xerrors.Errorf("failed to marshal graph to DOT: %w",err)
171+
}
172+
173+
returnstring(dotBytes),nil
174+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp