Skip to main content

swh_graph/views/spy/
mod.rs

1// Copyright (C) 2024-2026  The Software Heritage developers
2// See the AUTHORS file at the top-level directory of this distribution
3// License: GNU General Public License version 3, or any later version
4// See top-level LICENSE file for more information
5
6use std::collections::HashMap;
7use std::path::Path;
8use std::sync::{Arc, Mutex};
9
10use anyhow::Result;
11
12use crate::graph::*;
13use crate::labels::EdgeLabel;
14use crate::properties;
15use crate::{NodeType, SWHID};
16
17mod contents;
18mod label_names;
19mod maps;
20mod persons;
21mod strings;
22mod timestamps;
23
24/// Record of a property access operation
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum PropertyAccess {
27    // Maps
28    Swhid(NodeId),
29    NodeType(NodeId),
30    NodeId(SWHID),
31    NodeIdForInvalidBytes([u8; SWHID::BYTES_SIZE]),
32    NodeIdForInvalidStrArray([u8; 50]),
33    NodeIdForInvalidString(String),
34
35    // Contents
36    IsSkippedContent(NodeId),
37    ContentLength(NodeId),
38
39    // Persons
40    AuthorId(NodeId),
41    CommitterId(NodeId),
42
43    // Strings
44    Message(NodeId),
45    TagName(NodeId),
46
47    // Timestamps
48    AuthorTimestamp(NodeId),
49    AuthorTimestampOffset(NodeId),
50    CommitterTimestamp(NodeId),
51    CommitterTimestampOffset(NodeId),
52
53    // LabelNames
54    /// TODO: record exact queries (LabelNameId <-> label name)
55    LabelNames,
56}
57
58/// Record of a graph access operation
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub enum GraphAccessRecord {
61    // SwhGraph
62    Path,
63    IsTransposed,
64    NumNodes,
65    HasNode(NodeId),
66    NumArcs,
67    NumNodesByType,
68    NumArcsByType,
69    HasArc(NodeId, NodeId),
70
71    // SwhGraphWithProperties
72    Property(PropertyAccess),
73
74    // SwhForwardGraph
75    Successors(NodeId),
76    Outdegree(NodeId),
77
78    // SwhLabeledForwardGraph
79    LabeledSuccessors(NodeId),
80    UntypedLabeledSuccessors(NodeId),
81
82    // SwhBackwardGraph
83    Predecessors(NodeId),
84    Indegree(NodeId),
85
86    // SwhLabeledBackwardGraph
87    LabeledPredecessors(NodeId),
88    UntypedLabeledPredecessors(NodeId),
89}
90
91#[derive(Debug)]
92struct GraphSpyInner<G: SwhGraph> {
93    graph: G,
94}
95
96/// Wraps a graph, and records calls to its methods, useful for tests
97#[derive(Debug)]
98pub struct GraphSpy<
99    G: SwhGraph,
100    MAPS: properties::MaybeMaps = properties::NoMaps,
101    TIMESTAMPS: properties::MaybeTimestamps = properties::NoTimestamps,
102    PERSONS: properties::MaybePersons = properties::NoPersons,
103    CONTENTS: properties::MaybeContents = properties::NoContents,
104    STRINGS: properties::MaybeStrings = properties::NoStrings,
105    LABELNAMES: properties::MaybeLabelNames = properties::NoLabelNames,
106> {
107    inner: Arc<GraphSpyInner<G>>,
108    /// History of graph accesses
109    pub history: Arc<Mutex<Vec<GraphAccessRecord>>>,
110    properties:
111        properties::SwhGraphProperties<MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>,
112}
113
114impl<G: SwhGraph>
115    GraphSpy<
116        G,
117        properties::NoMaps,
118        properties::NoTimestamps,
119        properties::NoPersons,
120        properties::NoContents,
121        properties::NoStrings,
122        properties::NoLabelNames,
123    >
124{
125    pub fn new(graph: G) -> Self {
126        let path = graph.path().to_owned();
127        let num_nodes = graph.num_nodes();
128        GraphSpy {
129            inner: Arc::new(GraphSpyInner { graph }),
130            history: Arc::new(Mutex::new(Vec::new())),
131            properties: properties::SwhGraphProperties {
132                path,
133                num_nodes,
134                maps: properties::NoMaps,
135                timestamps: properties::NoTimestamps,
136                persons: properties::NoPersons,
137                contents: properties::NoContents,
138                strings: properties::NoStrings,
139                label_names: properties::NoLabelNames,
140                label_names_are_in_base64_order: Default::default(),
141            },
142        }
143    }
144}
145
146impl<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
147    GraphSpy<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
148where
149    G: SwhGraph,
150    MAPS: properties::MaybeMaps,
151    TIMESTAMPS: properties::MaybeTimestamps,
152    PERSONS: properties::MaybePersons,
153    CONTENTS: properties::MaybeContents,
154    STRINGS: properties::MaybeStrings,
155    LABELNAMES: properties::MaybeLabelNames,
156{
157    fn record(&self, record: GraphAccessRecord) {
158        self.history.lock().unwrap().push(record);
159    }
160
161    pub fn graph(&self) -> &G {
162        &self.inner.graph
163    }
164}
165
166impl<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES> SwhGraph
167    for GraphSpy<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
168where
169    G: SwhGraph,
170    MAPS: properties::MaybeMaps,
171    TIMESTAMPS: properties::MaybeTimestamps,
172    PERSONS: properties::MaybePersons,
173    CONTENTS: properties::MaybeContents,
174    STRINGS: properties::MaybeStrings,
175    LABELNAMES: properties::MaybeLabelNames,
176{
177    fn path(&self) -> &Path {
178        self.record(GraphAccessRecord::Path);
179        self.inner.graph.path()
180    }
181    fn is_transposed(&self) -> bool {
182        self.record(GraphAccessRecord::IsTransposed);
183        self.inner.graph.is_transposed()
184    }
185    fn num_nodes(&self) -> usize {
186        self.record(GraphAccessRecord::NumNodes);
187        self.inner.graph.num_nodes()
188    }
189    fn has_node(&self, node_id: NodeId) -> bool {
190        self.record(GraphAccessRecord::HasNode(node_id));
191        self.inner.graph.has_node(node_id)
192    }
193    fn num_arcs(&self) -> u64 {
194        self.record(GraphAccessRecord::NumArcs);
195        self.inner.graph.num_arcs()
196    }
197    fn num_nodes_by_type(&self) -> Result<HashMap<NodeType, usize>> {
198        self.record(GraphAccessRecord::NumNodesByType);
199        self.inner.graph.num_nodes_by_type()
200    }
201    fn num_arcs_by_type(&self) -> Result<HashMap<(NodeType, NodeType), usize>> {
202        self.record(GraphAccessRecord::NumArcsByType);
203        self.inner.graph.num_arcs_by_type()
204    }
205    fn has_arc(&self, src_node_id: NodeId, dst_node_id: NodeId) -> bool {
206        self.record(GraphAccessRecord::HasArc(src_node_id, dst_node_id));
207        self.inner.graph.has_arc(src_node_id, dst_node_id)
208    }
209}
210
211impl<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES> SwhForwardGraph
212    for GraphSpy<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
213where
214    G: SwhForwardGraph,
215    MAPS: properties::MaybeMaps,
216    TIMESTAMPS: properties::MaybeTimestamps,
217    PERSONS: properties::MaybePersons,
218    CONTENTS: properties::MaybeContents,
219    STRINGS: properties::MaybeStrings,
220    LABELNAMES: properties::MaybeLabelNames,
221{
222    type Successors<'succ>
223        = <G as SwhForwardGraph>::Successors<'succ>
224    where
225        Self: 'succ;
226
227    fn successors(&self, node_id: NodeId) -> Self::Successors<'_> {
228        self.record(GraphAccessRecord::Successors(node_id));
229        self.inner.graph.successors(node_id)
230    }
231    fn outdegree(&self, node_id: NodeId) -> usize {
232        self.record(GraphAccessRecord::Outdegree(node_id));
233        self.inner.graph.outdegree(node_id)
234    }
235}
236
237impl<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES> SwhLabeledForwardGraph
238    for GraphSpy<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
239where
240    G: SwhLabeledForwardGraph,
241    MAPS: properties::Maps,
242    TIMESTAMPS: properties::MaybeTimestamps,
243    PERSONS: properties::MaybePersons,
244    CONTENTS: properties::MaybeContents,
245    STRINGS: properties::MaybeStrings,
246    LABELNAMES: properties::MaybeLabelNames,
247{
248    type LabeledArcs<'arc>
249        = <G as SwhLabeledForwardGraph>::LabeledArcs<'arc>
250    where
251        Self: 'arc;
252    type LabeledSuccessors<'succ>
253        = <G as SwhLabeledForwardGraph>::LabeledSuccessors<'succ>
254    where
255        Self: 'succ;
256
257    fn untyped_labeled_successors(&self, node_id: NodeId) -> Self::LabeledSuccessors<'_> {
258        self.record(GraphAccessRecord::UntypedLabeledSuccessors(node_id));
259        self.inner.graph.untyped_labeled_successors(node_id)
260    }
261
262    fn labeled_successors(
263        &self,
264        node_id: NodeId,
265    ) -> impl Iterator<Item = (usize, impl Iterator<Item = EdgeLabel>)>
266           + IntoFlattenedLabeledArcsIterator<EdgeLabel>
267           + '_ {
268        self.record(GraphAccessRecord::LabeledSuccessors(node_id));
269        self.inner.graph.labeled_successors(node_id)
270    }
271}
272
273impl<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES> SwhBackwardGraph
274    for GraphSpy<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
275where
276    G: SwhBackwardGraph,
277    MAPS: properties::MaybeMaps,
278    TIMESTAMPS: properties::MaybeTimestamps,
279    PERSONS: properties::MaybePersons,
280    CONTENTS: properties::MaybeContents,
281    STRINGS: properties::MaybeStrings,
282    LABELNAMES: properties::MaybeLabelNames,
283{
284    type Predecessors<'succ>
285        = <G as SwhBackwardGraph>::Predecessors<'succ>
286    where
287        Self: 'succ;
288
289    fn predecessors(&self, node_id: NodeId) -> Self::Predecessors<'_> {
290        self.record(GraphAccessRecord::Predecessors(node_id));
291        self.inner.graph.predecessors(node_id)
292    }
293    fn indegree(&self, node_id: NodeId) -> usize {
294        self.record(GraphAccessRecord::Indegree(node_id));
295        self.inner.graph.indegree(node_id)
296    }
297}
298
299impl<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES> SwhLabeledBackwardGraph
300    for GraphSpy<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
301where
302    G: SwhLabeledBackwardGraph,
303    MAPS: properties::Maps,
304    TIMESTAMPS: properties::MaybeTimestamps,
305    PERSONS: properties::MaybePersons,
306    CONTENTS: properties::MaybeContents,
307    STRINGS: properties::MaybeStrings,
308    LABELNAMES: properties::MaybeLabelNames,
309{
310    type LabeledArcs<'arc>
311        = <G as SwhLabeledBackwardGraph>::LabeledArcs<'arc>
312    where
313        Self: 'arc;
314    type LabeledPredecessors<'succ>
315        = <G as SwhLabeledBackwardGraph>::LabeledPredecessors<'succ>
316    where
317        Self: 'succ;
318
319    fn untyped_labeled_predecessors(&self, node_id: NodeId) -> Self::LabeledPredecessors<'_> {
320        self.record(GraphAccessRecord::UntypedLabeledPredecessors(node_id));
321        self.inner.graph.untyped_labeled_predecessors(node_id)
322    }
323    fn labeled_predecessors(
324        &self,
325        node_id: NodeId,
326    ) -> impl IntoIterator<Item = (usize, impl Iterator<Item = crate::labels::EdgeLabel>)>
327           + IntoFlattenedLabeledArcsIterator<EdgeLabel>
328           + '_ {
329        self.record(GraphAccessRecord::LabeledPredecessors(node_id));
330        self.inner.graph.labeled_predecessors(node_id)
331    }
332}
333
334impl<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES> SwhGraphWithProperties
335    for GraphSpy<G, MAPS, TIMESTAMPS, PERSONS, CONTENTS, STRINGS, LABELNAMES>
336where
337    G: SwhGraphWithProperties,
338    MAPS: properties::MaybeMaps,
339    TIMESTAMPS: properties::MaybeTimestamps,
340    PERSONS: properties::MaybePersons,
341    CONTENTS: properties::MaybeContents,
342    STRINGS: properties::MaybeStrings,
343    LABELNAMES: properties::MaybeLabelNames,
344{
345    type Maps = MAPS;
346    type Timestamps = TIMESTAMPS;
347    type Persons = PERSONS;
348    type Contents = CONTENTS;
349    type Strings = STRINGS;
350    type LabelNames = LABELNAMES;
351
352    fn properties(
353        &self,
354    ) -> &properties::SwhGraphProperties<
355        Self::Maps,
356        Self::Timestamps,
357        Self::Persons,
358        Self::Contents,
359        Self::Strings,
360        Self::LabelNames,
361    > {
362        &self.properties
363    }
364}