kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/graph/graph.go (about) 1 /* 2 * Copyright 2017 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package graph defines the graph Service interface and some useful utility 18 // functions. 19 package graph // import "kythe.io/kythe/go/services/graph" 20 21 import ( 22 "context" 23 "fmt" 24 "math" 25 "net/http" 26 "sort" 27 "time" 28 29 "kythe.io/kythe/go/services/web" 30 "kythe.io/kythe/go/util/log" 31 32 cpb "kythe.io/kythe/proto/common_go_proto" 33 gpb "kythe.io/kythe/proto/graph_go_proto" 34 ) 35 36 // Service exposes direct access to nodes and edges in a Kythe graph. 37 type Service interface { 38 Nodes(context.Context, *gpb.NodesRequest) (*gpb.NodesReply, error) 39 Edges(context.Context, *gpb.EdgesRequest) (*gpb.EdgesReply, error) 40 } 41 42 // AllEdges returns all edges for a particular EdgesRequest. This means that 43 // the returned reply will not have a next page token. WARNING: the paging API 44 // exists for a reason; using this can lead to very large memory consumption 45 // depending on the request. 46 func AllEdges(ctx context.Context, es Service, req *gpb.EdgesRequest) (*gpb.EdgesReply, error) { 47 req.PageSize = math.MaxInt32 48 reply, err := es.Edges(ctx, req) 49 if err != nil || reply.NextPageToken == "" { 50 return reply, err 51 } 52 53 nodes, edges := NodesMap(reply.Nodes), EdgesMap(reply.EdgeSets) 54 55 for reply.NextPageToken != "" && err == nil { 56 req.PageToken = reply.NextPageToken 57 reply, err = es.Edges(ctx, req) 58 if err != nil { 59 return nil, err 60 } 61 nodesMapInto(reply.Nodes, nodes) 62 edgesMapInto(reply.EdgeSets, edges) 63 } 64 65 reply = &gpb.EdgesReply{ 66 Nodes: make(map[string]*cpb.NodeInfo, len(nodes)), 67 EdgeSets: make(map[string]*gpb.EdgeSet, len(edges)), 68 } 69 70 for ticket, facts := range nodes { 71 info := &cpb.NodeInfo{ 72 Facts: make(map[string][]byte, len(facts)), 73 } 74 for name, val := range facts { 75 info.Facts[name] = val 76 } 77 reply.Nodes[ticket] = info 78 } 79 80 for source, groups := range edges { 81 set := &gpb.EdgeSet{ 82 Groups: make(map[string]*gpb.EdgeSet_Group, len(groups)), 83 } 84 for kind, targets := range groups { 85 edges := make([]*gpb.EdgeSet_Group_Edge, 0, len(targets)) 86 for target, ordinals := range targets { 87 for ordinal := range ordinals { 88 edges = append(edges, &gpb.EdgeSet_Group_Edge{ 89 TargetTicket: target, 90 Ordinal: ordinal, 91 }) 92 } 93 } 94 sort.Sort(ByOrdinal(edges)) 95 set.Groups[kind] = &gpb.EdgeSet_Group{ 96 Edge: edges, 97 } 98 } 99 reply.EdgeSets[source] = set 100 } 101 102 return reply, err 103 } 104 105 // BoundedRequests guards against requests for more tickets than allowed per 106 // the MaxTickets configuration. 107 type BoundedRequests struct { 108 MaxTickets int 109 Service 110 } 111 112 // Nodes implements part of the Service interface. 113 func (b BoundedRequests) Nodes(ctx context.Context, req *gpb.NodesRequest) (*gpb.NodesReply, error) { 114 if len(req.Ticket) > b.MaxTickets { 115 return nil, fmt.Errorf("too many tickets requested: %d (max %d)", len(req.Ticket), b.MaxTickets) 116 } 117 return b.Service.Nodes(ctx, req) 118 } 119 120 // Edges implements part of the Service interface. 121 func (b BoundedRequests) Edges(ctx context.Context, req *gpb.EdgesRequest) (*gpb.EdgesReply, error) { 122 if len(req.Ticket) > b.MaxTickets { 123 return nil, fmt.Errorf("too many tickets requested: %d (max %d)", len(req.Ticket), b.MaxTickets) 124 } 125 return b.Service.Edges(ctx, req) 126 } 127 128 type webClient struct{ addr string } 129 130 // Nodes implements part of the Service interface. 131 func (w *webClient) Nodes(ctx context.Context, q *gpb.NodesRequest) (*gpb.NodesReply, error) { 132 var reply gpb.NodesReply 133 return &reply, web.Call(w.addr, "nodes", q, &reply) 134 } 135 136 // Edges implements part of the Service interface. 137 func (w *webClient) Edges(ctx context.Context, q *gpb.EdgesRequest) (*gpb.EdgesReply, error) { 138 var reply gpb.EdgesReply 139 return &reply, web.Call(w.addr, "edges", q, &reply) 140 } 141 142 // WebClient returns a graph Service based on a remote web server. 143 func WebClient(addr string) Service { 144 return &webClient{addr} 145 } 146 147 // RegisterHTTPHandlers registers JSON HTTP handlers with mux using the given 148 // graph Service. The following methods with be exposed: 149 // 150 // GET /nodes 151 // Request: JSON encoded graph.NodesRequest 152 // Response: JSON encoded graph.NodesReply 153 // GET /edges 154 // Request: JSON encoded graph.EdgesRequest 155 // Response: JSON encoded graph.EdgesReply 156 // 157 // Note: /nodes, and /edges will return their responses as serialized protobufs 158 // if the "proto" query parameter is set. 159 func RegisterHTTPHandlers(ctx context.Context, gs Service, mux *http.ServeMux) { 160 mux.HandleFunc("/nodes", func(w http.ResponseWriter, r *http.Request) { 161 start := time.Now() 162 defer func() { 163 log.InfoContextf(ctx, "graph.Nodes:\t%s", time.Since(start)) 164 }() 165 166 var req gpb.NodesRequest 167 if err := web.ReadJSONBody(r, &req); err != nil { 168 http.Error(w, err.Error(), http.StatusBadRequest) 169 return 170 } 171 reply, err := gs.Nodes(ctx, &req) 172 if err != nil { 173 http.Error(w, err.Error(), http.StatusInternalServerError) 174 return 175 } 176 if err := web.WriteResponse(w, r, reply); err != nil { 177 log.InfoContext(ctx, err) 178 } 179 }) 180 mux.HandleFunc("/edges", func(w http.ResponseWriter, r *http.Request) { 181 start := time.Now() 182 defer func() { 183 log.InfoContextf(ctx, "graph.Edges:\t%s", time.Since(start)) 184 }() 185 186 var req gpb.EdgesRequest 187 if err := web.ReadJSONBody(r, &req); err != nil { 188 http.Error(w, err.Error(), http.StatusBadRequest) 189 return 190 } 191 reply, err := gs.Edges(ctx, &req) 192 if err != nil { 193 http.Error(w, err.Error(), http.StatusInternalServerError) 194 return 195 } 196 if err := web.WriteResponse(w, r, reply); err != nil { 197 log.InfoContext(ctx, err) 198 } 199 }) 200 } 201 202 // NodesMap returns a map from each node ticket to a map of its facts. 203 func NodesMap(nodes map[string]*cpb.NodeInfo) map[string]map[string][]byte { 204 m := make(map[string]map[string][]byte, len(nodes)) 205 nodesMapInto(nodes, m) 206 return m 207 } 208 209 func nodesMapInto(nodes map[string]*cpb.NodeInfo, m map[string]map[string][]byte) { 210 for ticket, n := range nodes { 211 facts, ok := m[ticket] 212 if !ok { 213 facts = make(map[string][]byte, len(n.Facts)) 214 m[ticket] = facts 215 } 216 for name, value := range n.Facts { 217 facts[name] = value 218 } 219 } 220 } 221 222 // EdgesMap returns a map from each node ticket to a map of its outward edge kinds. 223 func EdgesMap(edges map[string]*gpb.EdgeSet) map[string]map[string]map[string]map[int32]struct{} { 224 m := make(map[string]map[string]map[string]map[int32]struct{}, len(edges)) 225 edgesMapInto(edges, m) 226 return m 227 } 228 229 func edgesMapInto(edges map[string]*gpb.EdgeSet, m map[string]map[string]map[string]map[int32]struct{}) { 230 for source, es := range edges { 231 kinds, ok := m[source] 232 if !ok { 233 kinds = make(map[string]map[string]map[int32]struct{}, len(es.Groups)) 234 m[source] = kinds 235 } 236 for kind, g := range es.Groups { 237 for _, e := range g.Edge { 238 targets, ok := kinds[kind] 239 if !ok { 240 targets = make(map[string]map[int32]struct{}) 241 kinds[kind] = targets 242 } 243 ordinals, ok := targets[e.TargetTicket] 244 if !ok { 245 ordinals = make(map[int32]struct{}) 246 targets[e.TargetTicket] = ordinals 247 } 248 ordinals[e.Ordinal] = struct{}{} 249 } 250 } 251 } 252 } 253 254 // ByOrdinal orders the edges in an edge group by their ordinals, with ties 255 // broken by ticket. 256 type ByOrdinal []*gpb.EdgeSet_Group_Edge 257 258 func (s ByOrdinal) Len() int { return len(s) } 259 func (s ByOrdinal) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 260 func (s ByOrdinal) Less(i, j int) bool { 261 if s[i].Ordinal == s[j].Ordinal { 262 return s[i].TargetTicket < s[j].TargetTicket 263 } 264 return s[i].Ordinal < s[j].Ordinal 265 }