Skip to content
Snippets Groups Projects
Commit ffe63d03 authored by Eddie Groh's avatar Eddie Groh
Browse files

Added automatic trigger for metric collection and executed large

refactor and cleanup as well as added developer documentation

Issue-Ref: 4310
Issue-Url: af3#4310



Signed-off-by: default avatarEddie Groh <groh@fortiss.org>
parent 0abba388
No related branches found
No related tags found
1 merge request!210Setting up Metric extraction plugin for AF3 : Issue 4310
Showing
with 613 additions and 308 deletions
......@@ -36,7 +36,7 @@ import org.fortiss.tooling.kernel.ui.extension.data.ContextMenuContextProvider;
/**
* Crates a context menu entry to generate the for the selected
*
* @author munaro
* @author groh
*/
public class ModelQualityExtractionMenu implements IContextMenuContributor {
......@@ -81,7 +81,7 @@ public class ModelQualityExtractionMenu implements IContextMenuContributor {
@Override
public void run() {
ITopLevelElement top = IPersistencyService.getInstance().getTopLevelElementFor(this.fp);
IModelQualityService.getInstance().performMetricAnalysis(top);
IModelQualityService.getInstance().scheduleMetricCollection(top);
}
}
}
......@@ -177,6 +177,11 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
Set<MetricKey> validKeys = new HashSet<>();
validKeys.addAll(node.getStoredDoubles().keySet());
validKeys.addAll(node.getStoredIntegers().keySet());
for(MetricTreeNode child : node.getChildren()) {
validKeys.addAll(child.getStoredDoubles().keySet());
validKeys.addAll(child.getStoredIntegers().keySet());
}
ObservableList<MetricKey> choices = choiceBox.getItems();
choices.retainAll(validKeys);
for(MetricKey key : validKeys) {
......@@ -256,7 +261,7 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
}
double maxval = children.stream().map(p -> p.getValueAsDouble(key)).filter(p -> p != null)
.mapToDouble(p -> p).max().getAsDouble();
.mapToDouble(p -> p).max().orElse(0.0);
chartStyle.setUseIndividualAxisSegments(false);
chartStyle.setTitleStyle(new FontStyle("Verdana", 14, BLUE.brighter()));
......@@ -264,10 +269,14 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
DataSeries elementData = new DataSeries("Data");
for(var child : node.getChildren()) {
Double value = child.getValueAsDouble(key);
String name = child.getName();
if(value == null) {
value = 0.0;
}
DoubleAxis testing = new DoubleAxis(child.getName(), 0.0, maxval);
if(name == null) {
name = "Impossible null";
}
DoubleAxis testing = new DoubleAxis(name, 0.0, maxval);
AxisStyle aStyle3Segs = new AxisStyle(SOLID_BLACK_1PT, BLACK_VERDANA_14PT, 3,
BLACK_VERDANA_8PT, new DecimalFormat("#.##"));
......
......@@ -6,7 +6,129 @@
<body lang="en-US" dir="ltr">
<h1>Developer Documentation for <i>Quality Functionality
(</i><i>org.fortiss.tooling.ext.quality</i><i>)</i></h1>
<p>Lorem Ipsum</p>
<p>Below is a list of all collected metrics. They are represented by the enum <code> org.fortiss.tooling.ext.quality.data.MetricKey</code></p>
<p>They are categorized by the respective provider collecting the metric</p>
<h2>HierarchicElementProvider</h2>
<code>org.fortiss.tooling.ext.quality.HierarchicElementProvider</code>
<h4>UNQIUE_ID</h4>
<p><code>Integer</code>: This is an unique identifier for each element as specified in <code>org.fortiss.tooling.kernel.model.IIdLabeled</code>. </p>
<h4>NUMBER_OF_CONNECTORS</h4>
<p><code>Integer</code>: Number of connectors in the respective element</p>
<h4>NUMBER_OF_CONTAINED_ELEMENTS</h4>
<p><code>Integer</code>: Number of entries in the containedElements list.</p>
<h4>NUMBER_OF_CONNECTIONS</h4>
<p><code>Integer</code>: Number of connections in this element. List of aggregated connection model elements. Usually a aggregates all connections of its direct sub-structure.</p>
<h4>NUMBER_OF_TOTAL_CONNECTORS</h4>
<p><code>Integer</code>: Total number of connectors in this element and all contained elements</p>
<h4>NUMBER_OF_TOTAL_ENTRY_CONNECTORS</h4>
<p><code>Integer</code>: Sum of all connectors which implement <code>org.fortiss.tooling.base.model.base.EntryConnectorBase</code></p>
<h4>NUMBER_OF_TOTAL_EXIT_CONNECTORS</h4>
<p><code>Integer</code>: Sum of all connectors which implement <code>org.fortiss.tooling.base.model.base.ExitConnectorBase</code>. Usually the sum over this and <code>NUMBER_OF_TOTAL_ENTRY_CONNECTORS</code> should equal <code>NUMBER_OF_TOTAL_CONNECTORS</code></p>
<h4>NUMBER_OF_TOTAL_ELEMENTS</h4>
<p><code>Integer</code>: Total number of elements contained in this element. Includes the element for which this metric is recorded.</p>
<h4>NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS</h4>
<p><code>Integer</code>: Total number of elements implementing <code>org.fortiss.tooling.kernel.model.INamedCommentedElement</code> in this element. Includes the element for which this metric is recorded. Never exceeds <code>NUMBER_OF_TOTAL_ELEMENTS</code>.</p>
<h4>NUMBER_OF_TOTAL_COMMENTED_ELEMENTS</h4>
<p><code>Integer</code>: Total number of elements which are commentable and do not have <code>null</code> or an empty string as comment</p>
<h4>NUMBER_OF_TOTAL_LEAF_ELEMENTS</h4>
<p><code>Integer</code>: Total number of elements contained in this element which do not contain elements. Includes self</p>
<h2>GraphMetricsProvider</h2>
<code>org.fortiss.tooling.ext.quality.GraphMetricsProvider</code>
<h4>BETWEENESS_CENTRALITY</h4>
<p><code>Double</code>: Value of the betweenness centrality of this element embedded in a graph of consisting of all elements which are contained in the element which contains this element, as well as all neighbours of these elements</p>
<p>The direction of the connections are respected, and it is assumed that all connections connect to a ExitConnectorBase or EntryConnectorBase</p>
<h4>BETWEENESS_CENTRALITY_RECURSIVELY</h4>
<p><code>Double</code>: Same as above, but all connections are resolved to leaf elements, i.e. elements which do not contain other elements</p>
<h4>CLUSTERING_COEFFICIENT</h4>
<p><code>Double</code>: Clustering coefficient of this element. Ignores connection direction and resolves all neighbours to leaf elements.</p>
<h2>ComponentMetricProvider</h2>
<code>org.fortiss.af3.component.quality.ComponentMetricProvider</code>
<h4>NUMBER_OF_TOTAL_CODE_CONSTANTS</h4>
<p><code>Integer</code>: Total number of constants in code specifications in this element and all contained elements</p>
<h4>NUMBER_OF_TOTAL_CODE_CYCLOMATIC_COMPLEXITY</h4>
<p><code>Integer</code>: The total cyclomatic complexity of all code specifications in this element and all contained elements</p>
<h4>NUMBER_OF_TOTAL_CODE_LINES</h4>
<p><code>Integer</code>: Total number of lines of code in all code specifications in this element and all contained elements</p>
<h4>NUMBER_OF_TOTAL_CODE_COMMENTS</h4>
<p><code>Integer</code>: Total number of comment blocks in all code specifications in this element and all contained elements</p>
<h2>ComponentMetricProvider</h2>
<code>org.fortiss.af3.component.quality.ComponentMetricProvider</code>
<h2>DataDictonaryProvider</h2>
<code>org.fortiss.af3.component.quality.DataDictonaryProvider</code>
<p> Provides Metrics also collected by the HierarchicElementProvider, but for the DataDictionary.</p>
<h2>SafetyMetricProvider</h2>
<code>org.fortiss.af3.safety.quality.SafetyMetricProvider</code>
<h4>SAFETY_LEVEL</h4>
<p><code>String</code>: String representation of the assigned safety level of this element</p>
<h2>AllocationTableProvider</h2>
<code>org.fortiss.af3.task.quality.AllocationTableProvider</code>
<h4>TASK_ALLOCATED_COMPONENTS</h4>
<p><code>Integer</code>: Number of allocated Components on this Task. This metric can only be non-null for <code>org.fortiss.af3.task.model.Task</code> Elements</p>
<h4>HARDWARE_ALLOCATED_TASKS</h4>
<p><code>Integer</code>: Number of allocated Tasks on this ExecutionUnit. This metric can only be non-null for <code>org.fortiss.af3.platform.model.ExecutionUnit</code> Elements</p>
<h2>ModelQualityService</h2>
<code>org.fortiss.tooling.ext.quality.service.ModelQualityService</code>
<h4>NESTING_LEVEL</h4>
<p><code>Integer</code>: The depth of this element. Defined by the number of elements which have to be traversed to reach this element from the root</p>
<h4>CONSTRAINT_VIOLATIONS_ERROR</h4>
<p><code>Integer</code>: Number of constraint violations of the severity error which have this element as source</p>
<h4>CONSTRAINT_VIOLATIONS_WARNING</h4>
<p><code>Integer</code>: Number of constraint violations of the severity warning which have this element as source</p>
</body>
</html>
\ No newline at end of file
/*-------------------------------------------------------------------------+
| Copyright 2023 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.ext.quality;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import org.eclipse.emf.common.util.EList;
import org.fortiss.tooling.base.model.base.EntryConnectorBase;
import org.fortiss.tooling.base.model.base.ExitConnectorBase;
import org.fortiss.tooling.base.model.element.IConnection;
import org.fortiss.tooling.base.model.element.IConnector;
import org.fortiss.tooling.base.model.element.IHierarchicElement;
import org.fortiss.tooling.ext.quality.data.MetricDataManager;
import org.fortiss.tooling.ext.quality.data.MetricKey;
import org.fortiss.tooling.kernel.utils.EcoreUtils;
/**
*
* @author groh
*/
public class GraphMetricsProvider {
/**
* Creates a list of nodes contained in the graph centered around the provided element
* It will contain all elements contained within the provided element as well as all elements
* connected to the provided element
*
* @param scopeElement
* element defining the scope of the graph
* @param recursively
* if true, everything is resolved to leaf elements
* @return node list
*/
private static Set<IHierarchicElement> getLocalGraphView(IHierarchicElement scopeElement,
boolean recursively) {
// Create a view consisting of all nodes inside this element, as well as all elements
// connected to this element
// This ensures that elements inside this element communicating with elements outside
// this element are properly recognized
Set<IHierarchicElement> graphNodes = new HashSet<>();
if(recursively) {
recursivelyGetLeafElements(scopeElement, graphNodes);
} else {
graphNodes.addAll(scopeElement.getContainedElements());
}
// Collect all elements to which currentElement has a outgoing connection
for(ExitConnectorBase exitConnector : EcoreUtils.pickInstanceOf(ExitConnectorBase.class,
scopeElement.getConnectors())) {
for(IConnection exitConnection : exitConnector.getOutgoing()) {
graphNodes.add(exitConnection.getTarget().getOwner());
}
}
// Collect all elements to which currentElement has a incoming connection
for(EntryConnectorBase entryConnector : EcoreUtils.pickInstanceOf(EntryConnectorBase.class,
scopeElement.getConnectors())) {
for(IConnection entryConnection : entryConnector.getIncoming()) {
graphNodes.add(entryConnection.getSource().getOwner());
}
}
return graphNodes;
}
/**
* Get all leafs inside the provided element
*
* @param element
* the element containing leafs
* @param allElements
* list where the leafs are saved
*/
private static void recursivelyGetLeafElements(IHierarchicElement element,
Set<IHierarchicElement> allElements) {
allElements.add(element);
for(IHierarchicElement containedElement : element.getContainedElements()) {
recursivelyGetLeafElements(containedElement, allElements);
}
}
/**
* Follows outgoing connections until the connector has no further outgoing connections and then
* saves the owner of this connector to the provided list
*
* @param connector
* starting point for search
* @param allElements
* list where the owners are collected
*/
private static void recursivlyFollowOutgoingConnection(IConnector connector,
Set<IHierarchicElement> allElements) {
EList<IConnection> outgoingConnections = connector.getOutgoing();
if(outgoingConnections.isEmpty()) {
allElements.add(connector.getOwner());
return;
}
for(IConnection outgoingConnection : outgoingConnections) {
recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(), allElements);
}
}
/**
* Follows incoming connections until the connector has no further incoming connections and
* then
* saves the owner of this connector to the provided list
*
* @param connector
* starting point for search
* @param allElements
* list where the owners are collected
*/
private static void recursivlyFollowIncomingConnection(IConnector connector,
Set<IHierarchicElement> allElements) {
EList<IConnection> incomingConnections = connector.getIncoming();
if(incomingConnections.isEmpty()) {
allElements.add(connector.getOwner());
return;
}
for(IConnection incomingConnection : incomingConnections) {
recursivlyFollowIncomingConnection(incomingConnection.getSource(), allElements);
}
}
/**
* Collects all neighbors regardless of the direction of connection
*
* @param element
* of which the neighbors should be collected
* @return set of the neighbors
*/
private static Set<IHierarchicElement> getUndirectedNeighbors(IHierarchicElement element) {
Set<IHierarchicElement> neighbors = new HashSet<>();
for(ExitConnectorBase exitConnectors : EcoreUtils.pickInstanceOf(ExitConnectorBase.class,
element.getConnectors())) {
recursivlyFollowOutgoingConnection(exitConnectors, neighbors);
}
for(EntryConnectorBase entryConnectors : EcoreUtils.pickInstanceOf(EntryConnectorBase.class,
element.getConnectors())) {
recursivlyFollowIncomingConnection(entryConnectors, neighbors);
}
return neighbors;
}
/**
* Calculates the clustering coefficient based on the neighborhood
*
* @param element
* of which the coefficient shall be calculated
* @return value of the coefficient
*/
public static double calculateClusteringCoefficent(IHierarchicElement element) {
Set<IHierarchicElement> neighbors = getUndirectedNeighbors(element);
int totalEdges = neighbors.size();
if(totalEdges < 2) {
return 0.0; // If there are fewer than 2 neighbors, the coefficient is undefined (or 0).
}
int triangles = 0;
for(IHierarchicElement neighbor : neighbors) {
Set<IHierarchicElement> neighborNeighbors = getUndirectedNeighbors(neighbor);
for(IHierarchicElement secondNeighbor : neighbors) {
if(secondNeighbor != neighbor && neighborNeighbors.contains(secondNeighbor)) {
triangles++;
}
}
}
double clusteringCoefficient = (double)triangles / (totalEdges * (totalEdges - 1));
return clusteringCoefficient;
}
/**
* Calculates and saves the betweenness centrality of all elements contained inside the provided
* scopeElement
*
* @param scopeElement
* the scope of this calculation
* @param manager
* location to save the metrics
* @param recursively
* if true, all calculation will be carried out on leaf elements
*/
public static void calculateBetweennessCentrality(IHierarchicElement scopeElement,
MetricDataManager manager, boolean recursively) {
// Abort if no elements are found
if(!scopeElement.getContainedElements().isEmpty()) {
Set<IHierarchicElement> graphNodes = getLocalGraphView(scopeElement, recursively);
// Initialize the betweenness values for all nodes to zero
Map<IHierarchicElement, Double> betweenness = new HashMap<>();
for(IHierarchicElement elementNode : graphNodes) {
betweenness.put(elementNode, 0.0);
}
// Iterate over all collected nodes
for(IHierarchicElement elementNode : graphNodes) {
// Initialize all variables
Stack<IHierarchicElement> stack = new Stack<>();
Map<IHierarchicElement, List<IHierarchicElement>> pred = new HashMap<>();
Map<IHierarchicElement, Integer> dist = new HashMap<>();
Map<IHierarchicElement, Double> sigma = new HashMap<>();
Queue<IHierarchicElement> queue = new LinkedList<>();
for(IHierarchicElement v : graphNodes) {
pred.put(v, new ArrayList<>());
dist.put(v, -1);
sigma.put(v, 0.0);
}
sigma.put(elementNode, 1.0);
dist.put(elementNode, 0);
queue.add(elementNode);
while(!queue.isEmpty()) {
IHierarchicElement v = queue.remove();
stack.push(v);
// Iterate over all connectors going out from the element
for(ExitConnectorBase exitConnectors : EcoreUtils
.pickInstanceOf(ExitConnectorBase.class, v.getConnectors())) {
// Iterate over all connections
for(IConnection outgoingConnection : exitConnectors.getOutgoing()) {
Set<IHierarchicElement> targetElements = new HashSet<>();
if(recursively) {
recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(),
targetElements);
} else {
IHierarchicElement primaryTargetElement =
outgoingConnection.getTarget().getOwner();
// When we have a connection to the scopeElement, we have a
// connection leaving the local scope
if(scopeElement == primaryTargetElement) {
// Continue searching for outgoing connections on the target
// Connector
IConnector targetConnector = outgoingConnection.getTarget();
for(IConnection secondaryOutgoingConnection : targetConnector
.getOutgoing()) {
targetElements.add(
secondaryOutgoingConnection.getTarget().getOwner());
}
} else {
targetElements.add(primaryTargetElement);
}
}
for(IHierarchicElement targetElement : targetElements) {
// Check if element is in node list, to avoid creating data for
// nodes outside of our graph
if(graphNodes.contains(targetElement)) {
if(dist.get(targetElement) < 0) {
queue.add(targetElement);
dist.put(targetElement, dist.get(v) + 1);
}
if(dist.get(targetElement) == dist.get(v) + 1) {
sigma.put(targetElement,
sigma.get(targetElement) + sigma.get(v));
pred.get(targetElement).add(v);
}
}
}
}
}
}
// Initialize delta
Map<IHierarchicElement, Double> delta = new HashMap<>();
for(IHierarchicElement v : graphNodes) {
delta.put(v, 0.0);
}
while(!stack.isEmpty()) {
IHierarchicElement w = stack.pop();
for(IHierarchicElement v : pred.get(w)) {
double c = (sigma.get(v) / sigma.get(w)) * (1.0 + delta.get(w));
delta.put(v, delta.get(v) + c);
}
if(!w.equals(elementNode)) {
betweenness.put(w, betweenness.get(w) + delta.get(w));
}
}
}
// Save metric
MetricKey key = recursively ? MetricKey.BETWEENESS_CENTRALITY_RECURSIVELY
: MetricKey.BETWEENESS_CENTRALITY;
for(IHierarchicElement child : scopeElement.getContainedElements()) {
manager.getTreeNodeLookupTable().get(child).getStoredDoubles().put(key,
betweenness.get(child));
}
}
}
}
......@@ -78,7 +78,7 @@ public class HierarchicElementProvider implements IMetricProvider<IHierarchicEle
integers.put(MetricKey.NUMBER_OF_CONTAINED_ELEMENTS,
currentElement.getContainedElements().size());
integers.put(MetricKey.NUMBER_OF_CHANNELS, currentElement.getConnections().size());
integers.put(MetricKey.NUMBER_OF_CONNECTIONS, currentElement.getConnections().size());
// depth metrics
integers.put(MetricKey.NUMBER_OF_TOTAL_CONNECTORS, connectors.size());
var entry_connectors = EcoreUtils.pickInstanceOf(EntryConnectorBase.class, connectors);
......
......@@ -35,7 +35,7 @@ public enum MetricKey {
/** Key for the metric counting elements */
NUMBER_OF_CONTAINED_ELEMENTS(false),
/** Key for the metric counting channels */
NUMBER_OF_CHANNELS(false),
NUMBER_OF_CONNECTIONS(false),
/** Safety level of the element */
SAFETY_LEVEL(false, true),
/** How deeply nested the corresponding element is */
......
......@@ -34,6 +34,6 @@ public interface IModelQualityService {
<T extends EObject> void registerMetricProvider(IMetricProvider<T> provider,
Class<T> modelElementClass);
/** analyses the metrics and processes them */
void performMetricAnalysis(ITopLevelElement top);
/** Schedules an analysis of the metrics and processes them */
void scheduleMetricCollection(ITopLevelElement top);
}
......@@ -19,46 +19,49 @@ import static java.util.Collections.emptyList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.fortiss.tooling.base.model.base.EntryConnectorBase;
import org.fortiss.tooling.base.model.base.ExitConnectorBase;
import org.fortiss.tooling.base.model.element.IConnection;
import org.fortiss.tooling.base.model.element.IConnector;
import org.fortiss.tooling.base.model.element.IHierarchicElement;
import org.fortiss.tooling.ext.quality.GraphMetricsProvider;
import org.fortiss.tooling.ext.quality.IMetricProvider;
import org.fortiss.tooling.ext.quality.data.MetricDataManager;
import org.fortiss.tooling.ext.quality.data.MetricKey;
import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
import org.fortiss.tooling.ext.quality.io.CSVFileWriter;
import org.fortiss.tooling.ext.quality.storage.CSVFileWriter;
import org.fortiss.tooling.kernel.extension.data.IConstraintViolation;
import org.fortiss.tooling.kernel.extension.data.IConstraintViolation.ESeverity;
import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
import org.fortiss.tooling.kernel.internal.ConstraintCheckerService;
import org.fortiss.tooling.kernel.introspection.IIntrospectionDetailsItem;
import org.fortiss.tooling.kernel.introspection.IIntrospectionItem;
import org.fortiss.tooling.kernel.introspection.IIntrospectiveKernelService;
import org.fortiss.tooling.kernel.model.IProjectRootElement;
import org.fortiss.tooling.kernel.service.IKernelIntrospectionSystemService;
import org.fortiss.tooling.kernel.service.IMigrationService;
import org.fortiss.tooling.kernel.service.IPersistencyService;
import org.fortiss.tooling.kernel.service.base.EObjectAwareServiceBase;
import org.fortiss.tooling.kernel.service.listener.IPersistencyServiceListener;
import org.fortiss.tooling.kernel.utils.EcoreUtils;
/**
* This class implements the {@link IMigrationService} interface.
*
* @author blaschke
* @author groh
*/
public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider<?>>
implements IIntrospectiveKernelService, IModelQualityService {
implements IIntrospectiveKernelService, IModelQualityService, IPersistencyServiceListener {
/** The singleton instance. */
private static final ModelQualityService INSTANCE = new ModelQualityService();
......@@ -81,10 +84,43 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
/** Static instance of the MetricDataManager */
public static MetricDataManager metricDataManagerInstance = new MetricDataManager();
/** Top-level elements queued to be processed by the metric collector. */
private final Queue<ITopLevelElement> queuedProcessableElements =
new ConcurrentLinkedQueue<ITopLevelElement>();
/** Stores the constraint checking job. */
private final Job metricCollectorJob = new Job("Collecting metrics ...") {
@Override
protected IStatus run(IProgressMonitor monitor) {
do {
if(monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
ITopLevelElement toBeProcessed = queuedProcessableElements.poll();
if(toBeProcessed == null) {
return Status.OK_STATUS;
}
performMetricCollection(toBeProcessed);
} while(true);
}
};
/** {@inheritDoc} */
@Override
public void scheduleMetricCollection(ITopLevelElement element) {
queuedProcessableElements.add(element);
metricCollectorJob.schedule();
}
/** {@inheritDoc} */
@Override
public void startService() {
IKernelIntrospectionSystemService.getInstance().registerService(this);
IPersistencyService.getInstance().addTopLevelElementListener(this);
}
/** Registers the migration provider with the service. */
......@@ -108,9 +144,7 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
* @param topLvl
* the file project on which the analysis should be performed
*/
@Override
public void performMetricAnalysis(ITopLevelElement topLvl) {
private void performMetricCollection(ITopLevelElement topLvl) {
// Sort root elements, as some metrics are saved into trees from other root elements
// To avoid these problems, the elements have to be processed in a certain order
......@@ -145,6 +179,29 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
collectMetrics(rootNode, rootElement);
}
// Collect all violated constraints and group them by their respective source
List<IConstraintViolation<? extends EObject>> contraintViolations =
ConstraintCheckerService.getInstance()
.performAllConstraintChecksRecursively(rootElement);
for(Entry<? extends EObject, List<IConstraintViolation<? extends EObject>>> entry : contraintViolations
.stream().collect(Collectors.groupingBy(IConstraintViolation::getSource))
.entrySet()) {
MetricTreeNode node =
metricDataManagerInstance.getTreeNodeLookupTable().get(entry.getKey());
if(node != null) {
// Collect violations and store them in the error and warning metric
List<IConstraintViolation<? extends EObject>> violations = entry.getValue();
var integers = node.getStoredIntegers();
integers.put(MetricKey.CONSTRAINT_VIOLATIONS_ERROR, (int)violations.stream()
.filter(v -> v.getSeverity() == ESeverity.ERROR).count());
integers.put(MetricKey.CONSTRAINT_VIOLATIONS_WARNING, (int)violations.stream()
.filter(v -> v.getSeverity() == ESeverity.WARNING).count());
}
}
root_nodes.put(rootElement, rootNode);
metricDataManagerInstance.getTreeNodeLookupTable().put(rootElement, rootNode);
}
......@@ -213,288 +270,10 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
manager.getTreeNodeLookupTable().put(currentElement, node);
calculateBetweennessCentrality(currentElement, manager, false);
calculateBetweennessCentrality(currentElement, manager, true);
GraphMetricsProvider.calculateBetweennessCentrality(currentElement, manager, false);
GraphMetricsProvider.calculateBetweennessCentrality(currentElement, manager, true);
node.getStoredDoubles().put(MetricKey.CLUSTERING_COEFFICIENT,
calculateClusteringCoefficent(currentElement));
}
}
/**
* Creates a list of nodes contained in the graph centered around the provided element
* It will contain all elements contained within the provided element as well as all elements
* connected to the provided element
*
* @param scopeElement
* element defining the scope of the graph
* @param recursively
* if true, everything is resolved to leaf elements
* @return node list
*/
private static Set<IHierarchicElement> getLocalGraphView(IHierarchicElement scopeElement,
boolean recursively) {
// Create a view consisting of all nodes inside this element, as well as all elements
// connected to this element
// This ensures that elements inside this element communicating with elements outside
// this element are properly recognized
Set<IHierarchicElement> graphNodes = new HashSet<>();
if(recursively) {
recursivelyGetLeafElements(scopeElement, graphNodes);
} else {
graphNodes.addAll(scopeElement.getContainedElements());
}
// Collect all elements to which currentElement has a outgoing connection
for(ExitConnectorBase exitConnector : EcoreUtils.pickInstanceOf(ExitConnectorBase.class,
scopeElement.getConnectors())) {
for(IConnection exitConnection : exitConnector.getOutgoing()) {
graphNodes.add(exitConnection.getTarget().getOwner());
}
}
// Collect all elements to which currentElement has a incoming connection
for(EntryConnectorBase entryConnector : EcoreUtils.pickInstanceOf(EntryConnectorBase.class,
scopeElement.getConnectors())) {
for(IConnection entryConnection : entryConnector.getIncoming()) {
graphNodes.add(entryConnection.getSource().getOwner());
}
}
return graphNodes;
}
/**
* Get all leafs inside the provided element
*
* @param element
* the element containing leafs
* @param allElements
* list where the leafs are saved
*/
private static void recursivelyGetLeafElements(IHierarchicElement element,
Set<IHierarchicElement> allElements) {
allElements.add(element);
for(IHierarchicElement containedElement : element.getContainedElements()) {
recursivelyGetLeafElements(containedElement, allElements);
}
}
/**
* Follows outgoing connections until the connector has no further outgoing connections and then
* saves the owner of this connector to the provided list
*
* @param connector
* starting point for search
* @param allElements
* list where the owners are collected
*/
private static void recursivlyFollowOutgoingConnection(IConnector connector,
Set<IHierarchicElement> allElements) {
EList<IConnection> outgoingConnections = connector.getOutgoing();
if(outgoingConnections.isEmpty()) {
allElements.add(connector.getOwner());
return;
}
for(IConnection outgoingConnection : outgoingConnections) {
recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(), allElements);
}
}
/**
* Follows incoming connections until the connector has no further incoming connections and
* then
* saves the owner of this connector to the provided list
*
* @param connector
* starting point for search
* @param allElements
* list where the owners are collected
*/
private static void recursivlyFollowIncomingConnection(IConnector connector,
Set<IHierarchicElement> allElements) {
EList<IConnection> incomingConnections = connector.getIncoming();
if(incomingConnections.isEmpty()) {
allElements.add(connector.getOwner());
return;
}
for(IConnection incomingConnection : incomingConnections) {
recursivlyFollowIncomingConnection(incomingConnection.getSource(), allElements);
}
}
/**
* Collects all neighbors regardless of the direction of connection
*
* @param element
* of which the neighbors should be collected
* @return set of the neighbors
*/
private static Set<IHierarchicElement> getUndirectedNeighbors(IHierarchicElement element) {
Set<IHierarchicElement> neighbors = new HashSet<>();
for(ExitConnectorBase exitConnectors : EcoreUtils.pickInstanceOf(ExitConnectorBase.class,
element.getConnectors())) {
recursivlyFollowOutgoingConnection(exitConnectors, neighbors);
}
for(EntryConnectorBase entryConnectors : EcoreUtils.pickInstanceOf(EntryConnectorBase.class,
element.getConnectors())) {
recursivlyFollowIncomingConnection(entryConnectors, neighbors);
}
return neighbors;
}
/**
* Calculates the clustering coefficient based on the neighborhood
*
* @param element
* of which the coefficient shall be calculated
* @return value of the coefficient
*/
private static double calculateClusteringCoefficent(IHierarchicElement element) {
Set<IHierarchicElement> neighbors = getUndirectedNeighbors(element);
int totalEdges = neighbors.size();
if(totalEdges < 2) {
return 0.0; // If there are fewer than 2 neighbors, the coefficient is undefined (or 0).
}
int triangles = 0;
for(IHierarchicElement neighbor : neighbors) {
Set<IHierarchicElement> neighborNeighbors = getUndirectedNeighbors(neighbor);
for(IHierarchicElement secondNeighbor : neighbors) {
if(secondNeighbor != neighbor && neighborNeighbors.contains(secondNeighbor)) {
triangles++;
}
}
}
double clusteringCoefficient = (double)triangles / (totalEdges * (totalEdges - 1));
return clusteringCoefficient;
}
/**
* Calculates and saves the betweenness centrality of all elements contained inside the provided
* scopeElement
*
* @param scopeElement
* the scope of this calculation
* @param manager
* location to save the metrics
* @param recursively
* if true, all calculation will be carried out on leaf elements
*/
private static void calculateBetweennessCentrality(IHierarchicElement scopeElement,
MetricDataManager manager, boolean recursively) {
// Abort if no elements are found
if(!scopeElement.getContainedElements().isEmpty()) {
Set<IHierarchicElement> graphNodes = getLocalGraphView(scopeElement, recursively);
// Initialize the betweenness values for all nodes to zero
Map<IHierarchicElement, Double> betweenness = new HashMap<>();
for(IHierarchicElement elementNode : graphNodes) {
betweenness.put(elementNode, 0.0);
}
// Iterate over all collected nodes
for(IHierarchicElement elementNode : graphNodes) {
// Initialize all variables
Stack<IHierarchicElement> stack = new Stack<>();
Map<IHierarchicElement, List<IHierarchicElement>> pred = new HashMap<>();
Map<IHierarchicElement, Integer> dist = new HashMap<>();
Map<IHierarchicElement, Double> sigma = new HashMap<>();
Queue<IHierarchicElement> queue = new LinkedList<>();
for(IHierarchicElement v : graphNodes) {
pred.put(v, new ArrayList<>());
dist.put(v, -1);
sigma.put(v, 0.0);
}
sigma.put(elementNode, 1.0);
dist.put(elementNode, 0);
queue.add(elementNode);
while(!queue.isEmpty()) {
IHierarchicElement v = queue.remove();
stack.push(v);
// Iterate over all connectors going out from the element
for(ExitConnectorBase exitConnectors : EcoreUtils
.pickInstanceOf(ExitConnectorBase.class, v.getConnectors())) {
// Iterate over all connections
for(IConnection outgoingConnection : exitConnectors.getOutgoing()) {
Set<IHierarchicElement> targetElements = new HashSet<>();
if(recursively) {
recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(),
targetElements);
} else {
IHierarchicElement primaryTargetElement =
outgoingConnection.getTarget().getOwner();
// When we have a connection to the scopeElement, we have a
// connection leaving the local scope
if(scopeElement == primaryTargetElement) {
// Continue searching for outgoing connections on the target
// Connector
IConnector targetConnector = outgoingConnection.getTarget();
for(IConnection secondaryOutgoingConnection : targetConnector
.getOutgoing()) {
targetElements.add(
secondaryOutgoingConnection.getTarget().getOwner());
}
} else {
targetElements.add(primaryTargetElement);
}
}
for(IHierarchicElement targetElement : targetElements) {
// Check if element is in node list, to avoid creating data for
// nodes outside of our graph
if(graphNodes.contains(targetElement)) {
if(dist.get(targetElement) < 0) {
queue.add(targetElement);
dist.put(targetElement, dist.get(v) + 1);
}
if(dist.get(targetElement) == dist.get(v) + 1) {
sigma.put(targetElement,
sigma.get(targetElement) + sigma.get(v));
pred.get(targetElement).add(v);
}
}
}
}
}
}
// Initialize delta
Map<IHierarchicElement, Double> delta = new HashMap<>();
for(IHierarchicElement v : graphNodes) {
delta.put(v, 0.0);
}
while(!stack.isEmpty()) {
IHierarchicElement w = stack.pop();
for(IHierarchicElement v : pred.get(w)) {
double c = (sigma.get(v) / sigma.get(w)) * (1.0 + delta.get(w));
delta.put(v, delta.get(v) + c);
}
if(!w.equals(elementNode)) {
betweenness.put(w, betweenness.get(w) + delta.get(w));
}
}
}
// Save metric
MetricKey key = recursively ? MetricKey.BETWEENESS_CENTRALITY_RECURSIVELY
: MetricKey.BETWEENESS_CENTRALITY;
for(IHierarchicElement child : scopeElement.getContainedElements()) {
manager.getTreeNodeLookupTable().get(child).getStoredDoubles().put(key,
betweenness.get(child));
}
GraphMetricsProvider.calculateClusteringCoefficent(currentElement));
}
}
......@@ -571,4 +350,28 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
public IIntrospectionDetailsItem getDetailsItem() {
return null;
}
/** {@inheritDoc} */
@Override
public void topLevelElementLoaded(ITopLevelElement element) {
// Nothing done
}
/** {@inheritDoc} */
@Override
public void topLevelElementAdded(ITopLevelElement element) {
// Nothing done
}
/** {@inheritDoc} */
@Override
public void topLevelElementRemoved(ITopLevelElement element) {
// Nothing done
}
/** {@inheritDoc} */
@Override
public void topLevelElementContentChanged(ITopLevelElement element) {
scheduleMetricCollection(element);
}
}
......@@ -13,16 +13,16 @@
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.ext.quality.io;
package org.fortiss.tooling.ext.quality.storage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
......@@ -56,8 +56,15 @@ public class CSVFileWriter {
return;
}
if(!ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR.exists()) {
ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR.mkdirs();
}
// path to csv file
Path path = Paths.get("data_mega_cool.csv");
Path path =
new File(ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR, "metric_logfile.csv")
.toPath();
List<String> allKeys = new ArrayList<>();
boolean createNewIndex = true;
......
/*-------------------------------------------------------------------------+
| Copyright 2021 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.ext.quality.storage;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import java.io.File;
import org.eclipse.core.resources.IWorkspaceRoot;
/**
* Manages the local log of the model quality service.
*
* @author groh
*/
public class ModelQualityStorageManager {
/** The name of the general reuse (library) directory. */
public static final String AF3_QUALITY_DIRECTORY_NAME = "AF3-Model-Quality-Directory";
/** AF3 reuse library files extension */
public static final String AF3_MODEL_QUALITY_FILES_EXTENSION = "af3_mq";
/** The current workspace root. */
public static final IWorkspaceRoot WORKSPACE_ROOT = getWorkspace().getRoot();
/** The File handler of the reuse project. */
public static final File MODEL_QUALITY_PROJECT_DIR =
new File(WORKSPACE_ROOT.getLocation() + File.separator + AF3_QUALITY_DIRECTORY_NAME);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment