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  }