diff --git a/api/client/swh/graph/client.py b/api/client/swh/graph/client.py index b578e83..00f1abe 100644 --- a/api/client/swh/graph/client.py +++ b/api/client/swh/graph/client.py @@ -1,34 +1,34 @@ # Copyright (C) 2019 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from swh.core.api import SWHRemoteAPI class GraphAPIError(Exception): """Graph API Error""" def __str__(self): return ('An unexpected error occurred in the Graph backend: {}' .format(self.args)) class RemoteGraphClient(SWHRemoteAPI): """Client to the Software Heritage Graph.""" def __init__(self, url, timeout=None): super().__init__( api_exception=GraphAPIError, url=url, timeout=timeout) # Web API endpoints def visit(self, swh_id, edges=None, traversal="dfs", direction="forward"): return self.get('visit/{}'.format(swh_id), params={ 'edges': edges, 'traversal': traversal, 'direction': direction }) - def stats(self, src_type, dst_type): - return self.get('stats/{}/{}'.format(src_type, dst_type)) + def stats(self): + return self.get('stats') diff --git a/api/docs/api.rst b/api/docs/api.rst index e248d16..ec04058 100644 --- a/api/docs/api.rst +++ b/api/docs/api.rst @@ -1,87 +1,81 @@ Graph traversal API =================== Visit ----- .. http:get:: /graph/visit/:swh_id Performs a graph traversal and returns explored paths (in the order of the traversal). :param string swh_id: starting point for the traversal, represented as a `Software Heritage persitent identifier `_ :query string edges: edges types the traversal can follow, expressed as a comma-separated list of ``src:dst`` strings (e.g.: ``"rev:rev,rev:dir"``). If no string is provided, the traversal will use all types of edges. :query string traversal: default to ``dfs`` :query string direction: can be either ``forward`` or ``backward``, default to ``forward`` :statuscode 200: no error :statuscode 400: an invalid query string has been provided :statuscode 404: requested starting node cannot be found in the graph .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "paths": [ [ "swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2", ... ], [ "swh:1:dir:d198bc9d7a6bcf6db04f476d29314f157507d505", ... ], ... ] } Stats ----- -.. http:get:: /graph/stats/:src_type/:dst_type +.. http:get:: /graph/stats Returns statistics on the compressed graph. - :param string src_type: can either be ``dir``, ``ori``, ``rel``, ``rev``, or - ``snp`` - :param string dst_type: can either be ``cnt``, ``dir``, ``obj``, ``rev``, or - ``snp`` - :statuscode 200: no error - :statuscode 404: requested subgraph cannot be found .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "counts": { "nodes": 16222788, "edges": 9907464 }, "ratios": { "compression": 0.367, "bits_per_node": 5.846, "bits_per_edge": 9.573, "avg_locality": 270.369 }, "indegree": { "min": 0, "max": 12382, "avg": 0.6107127825377487 }, "outdegree": { "min": 0, "max": 1, "avg": 0.6107127825377487 } } diff --git a/api/server/src/main/java/org/softwareheritage/graph/App.java b/api/server/src/main/java/org/softwareheritage/graph/App.java index ef070aa..9f60930 100644 --- a/api/server/src/main/java/org/softwareheritage/graph/App.java +++ b/api/server/src/main/java/org/softwareheritage/graph/App.java @@ -1,57 +1,54 @@ package org.softwareheritage.graph; import java.io.IOException; import java.util.Optional; import io.javalin.Javalin; import org.softwareheritage.graph.Graph; import org.softwareheritage.graph.SwhId; import org.softwareheritage.graph.algo.Stats; import org.softwareheritage.graph.algo.Visit; public class App { public static void main(String[] args) throws IOException, Exception { String path = args[0]; Graph graph = new Graph(path); + Stats stats = new Stats(path); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { graph.cleanUp(); } }); Javalin app = Javalin.create().start(5010); - app.get("/stats/:src_type/:dst_type", ctx -> { + app.get("/stats", ctx -> { try { - String srcType = ctx.pathParam("src_type"); - String dstType = ctx.pathParam("dst_type"); - ctx.json(new Stats(srcType, dstType)); - } catch (IllegalArgumentException e) { - ctx.status(404); + ctx.json(stats); } catch (Exception e) { ctx.status(400); ctx.result(e.toString()); } }); app.get("/visit/:swh_id", ctx -> { try { SwhId start = new SwhId(ctx.pathParam("swh_id")); // By default, traversal is a forward DFS using all edges String algorithm = Optional.ofNullable(ctx.queryParam("traversal")).orElse("dfs"); String direction = Optional.ofNullable(ctx.queryParam("direction")).orElse("forward"); String edges = Optional.ofNullable(ctx.queryParam("edges")).orElse("cnt:dir:rel:rev:snp"); ctx.json(new Visit(graph, start, edges, algorithm, direction)); } catch (IllegalArgumentException e) { ctx.status(400); ctx.result(e.toString()); } }); app.error(404, ctx -> { ctx.result("Not found"); }); } } diff --git a/api/server/src/main/java/org/softwareheritage/graph/algo/Stats.java b/api/server/src/main/java/org/softwareheritage/graph/algo/Stats.java index 0862de6..5075d42 100644 --- a/api/server/src/main/java/org/softwareheritage/graph/algo/Stats.java +++ b/api/server/src/main/java/org/softwareheritage/graph/algo/Stats.java @@ -1,61 +1,59 @@ package org.softwareheritage.graph.algo; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import org.softwareheritage.graph.Graph; /* TODO: - - add check for srcType_to_dstType - merge the two stats files (.properties and .stats) into one */ public class Stats { public long nbNodes; public long nbEdges; public double compressionRatio; public double bitsPerNode; public double bitsPerEdge; public double avgLocality; public long minIndegree; public long maxIndegree; public double avgIndegree; public long minOutdegree; public long maxOutdegree; public double avgOutdegree; - public Stats(String srcType, String dstType) throws IOException { + public Stats(String graphPath) throws IOException { HashMap statsMap = new HashMap<>(); // Parse statistics from generated files - Path statsPath = Paths.get("stats", srcType + "_to_" + dstType); - Path dotProperties = Paths.get(statsPath + ".properties"); - Path dotStats = Paths.get(statsPath + ".stats"); + Path dotProperties = Paths.get(graphPath + ".properties"); + Path dotStats = Paths.get(graphPath + ".stats"); List lines = Files.readAllLines(dotProperties); lines.addAll(Files.readAllLines(dotStats)); for (String line : lines) { String[] parts = line.split("="); if (parts.length == 2) { statsMap.put(parts[0], parts[1]); } } this.nbNodes = Long.parseLong(statsMap.get("nodes")); this.nbEdges = Long.parseLong(statsMap.get("arcs")); this.compressionRatio = Double.parseDouble(statsMap.get("compratio")); this.bitsPerNode = Double.parseDouble(statsMap.get("bitspernode")); this.bitsPerEdge = Double.parseDouble(statsMap.get("bitsperlink")); this.avgLocality = Double.parseDouble(statsMap.get("avglocality")); this.minIndegree = Long.parseLong(statsMap.get("minindegree")); this.maxIndegree = Long.parseLong(statsMap.get("maxindegree")); this.avgIndegree = Double.parseDouble(statsMap.get("avgindegree")); this.minOutdegree = Long.parseLong(statsMap.get("minoutdegree")); this.maxOutdegree = Long.parseLong(statsMap.get("maxoutdegree")); this.avgOutdegree = Double.parseDouble(statsMap.get("avgoutdegree")); } }