Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
59f3f44
Initial commit
srgjc Jun 18, 2025
44e3de7
refac class gen and msg mapping
srgjc Jun 29, 2025
0be8af4
refac multiple debugpy ops
srgjc Jun 30, 2025
f4d8201
impl control flow ops and refac vm class mappings
srgjc Jul 1, 2025
8afa9a0
fix get instance fields dynamically with id
srgjc Jul 1, 2025
7b24e26
fix include monitor button icon resources
srgjc Jul 1, 2025
a84b309
Add missing annotation
srgjc Jul 3, 2025
b227d41
Fix suspend/resume logic
srgjc Jul 3, 2025
c0817e4
Add vm method mapping, todo add logs
srgjc Jul 5, 2025
c79335f
Add set breakpoint functionality
srgjc Jul 7, 2025
b1a903a
rm class-level type annotations in favor of constructor type hints
srgjc Jul 8, 2025
1349e0f
add all return lines to py method raw
srgjc Jul 8, 2025
b0f8fc7
fix json value string wrapping for object mapping and add bool support
srgjc Jul 8, 2025
ee31421
update pattern compilation static final
srgjc Jul 9, 2025
1e23032
Refactor set breakpoint and add breakpoint state
srgjc Jul 14, 2025
fafbdd9
refac breakpoint handling
srgjc Jul 14, 2025
1930d07
refac breakpoint handling and fix vm object id
srgjc Jul 18, 2025
193fdbb
Fix method id and up-to-date ref from controller
srgjc Jul 22, 2025
2bfc3aa
Fix setBreakpoints cumulative
srgjc Jul 22, 2025
e0615ae
Fix field id, add DAP and USE values, add Sequence support
srgjc Jul 23, 2025
5a7ebf1
Add tuple support
srgjc Jul 23, 2025
5798968
Add method call this object ref
srgjc Jul 27, 2025
47537ca
Add method call runtime arg vals
srgjc Jul 27, 2025
381d0ce
refac fetch py method by id
srgjc Jul 27, 2025
4e81b8d
add float support
srgjc Jul 27, 2025
d293469
Add object ref use value and association support
srgjc Aug 5, 2025
31b2c09
Add set support
srgjc Aug 5, 2025
037dbab
add dict support wip
srgjc Aug 5, 2025
29c71d7
add attr mod support
srgjc Aug 5, 2025
fdb694c
Add SUM dir setting
srgjc Aug 10, 2025
4e31b66
rm method overloading logic
srgjc Aug 10, 2025
3012344
support multiple instances and rm unused field value
srgjc Aug 10, 2025
78e9bdf
rm hint constraint
srgjc Aug 12, 2025
34fefb2
register field mod workaround after type hint removal
srgjc Aug 12, 2025
eb84a2e
rm raw python type variants and base type
srgjc Aug 12, 2025
5a8dc4b
Refactor adapter and debugpy client
srgjc Aug 19, 2025
263bca5
Fix redundant internal type mapping
srgjc Aug 19, 2025
dc0f985
Support setting fields for objs on missed constructor call
srgjc Aug 19, 2025
e651ac1
Support default main module
srgjc Aug 25, 2025
3a2debd
Fix field resolution and mod registry
srgjc Aug 28, 2025
097c666
Refac event handling
srgjc Aug 29, 2025
04cbe4d
Remove unused interface
srgjc Aug 29, 2025
a8f0b9f
Support multiple classes in file for class name resolution
srgjc Aug 30, 2025
e044f5d
Refac internal breakpoint mappings update and breakpoint setting
srgjc Aug 30, 2025
eda85d8
Fix and refac lazy init of type members of interest
srgjc Oct 20, 2025
53818a4
Add module level op support as global
srgjc Oct 21, 2025
e768053
Separate concerns
srgjc Oct 22, 2025
213416e
Fix relative file path
srgjc Oct 28, 2025
dd6ced3
Rm constructor argument vmField restriction
srgjc Oct 28, 2025
e97a430
Fix constructor breakpoint placement
srgjc Oct 28, 2025
deeee88
Add continued event
srgjc Nov 5, 2025
873179b
Remove unused code
srgjc Nov 17, 2025
b70ad75
Update check for class existence
srgjc Nov 17, 2025
7a91e6d
Update PyMethod construction
srgjc Nov 17, 2025
e2514a5
Add max instance functionality
srgjc Nov 17, 2025
3a0f77b
Update robust request response matching
srgjc Nov 17, 2025
e52689b
Clean up
srgjc Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
target
*.iml
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
## USE Monitor
The USE monitor allows for monitoring running applications and to verify the applications behave as defined in a USE model.

### Requirements

Add the following vm argument to the `use` start script:

```
--add-exports=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED
```
32 changes: 32 additions & 0 deletions adapter/Python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# USE Monitor Python Adapter

USE monitor plugin that adds support for the monitoring of Python programs.

## Adapter Settings
- Host: Hostname of the machine, where the debuggee program is located.
- Port: Port on which the Debugpy server running the Python program is listening to.
- Workspace: Absolute path to the root directory of the Python program to monitor.
- Max. Instances: Maximum number of instances that USE should map and display.

## How to use the plugin
1. In the USE project under `use/use-core/target` create the following folder structure:
`mkdir -p lib/plugins/monitor_adapter`
2. Run `mvn clean package`
3. Copy the `MonitorAdapter_Python-1.0-SNAPSHOT.jar` to `lib/plugins/monitor_adapter`
4. Copy the `monitor-1.0-SNAPSHOT.jar` to `lib/plugins`
5. Copy the `monitor/lib/lablib-checkboxtree-3.2.jar` to `lib`
6. Add an IntelliJ run configuration with the main class `org.tzi.use.main.Main`, VM option `--add-exports=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED` and class path `use-gui`
7. Modify the classpath with the `lib` folder addition

## How to start the Debugpy server

```
#!/usr/bin/env bash

mkdir -p logs

debugpy \
--listen 5678 \
--log-to ./logs/ \
./<your_script>.py
```
58 changes: 58 additions & 0 deletions adapter/Python/dependency-reduced-pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>plugin_monitor</artifactId>
<groupId>org.tzi.use</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>MonitorAdapter_Python</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.tzi.use</groupId>
<artifactId>use-core</artifactId>
<version>7.1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.tzi.use</groupId>
<artifactId>use-gui</artifactId>
<version>7.1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.tzi.use</groupId>
<artifactId>monitor</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>lablib-checkboxtree</artifactId>
<groupId>it.cnr.imaa.essi</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
67 changes: 67 additions & 0 deletions adapter/Python/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.tzi.use</groupId>
<artifactId>plugin_monitor</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>MonitorAdapter_Python</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.tzi.use</groupId>
<artifactId>use-core</artifactId>
</dependency>
<dependency>
<groupId>org.tzi.use</groupId>
<artifactId>use-gui</artifactId>
</dependency>
<dependency>
<groupId>org.tzi.use</groupId>
<artifactId>monitor</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.19.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
1 change: 1 addition & 0 deletions adapter/Python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
debugpy==1.18.17
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package org.tzi.use.monitor.adapter.python;

import org.tzi.use.monitor.adapter.python.dap.custom.DAPValue;
import org.tzi.use.monitor.adapter.python.dap.StackFrame;
import org.tzi.use.monitor.adapter.python.dap.StoppedEventClass;
import org.tzi.use.monitor.plugins.monitor.vm.mm.python.*;
import org.tzi.use.plugins.monitor.Monitor;
import org.tzi.use.uml.ocl.value.Value;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;

/**
* This class is responsible for handling breakpoint events for
* constructor calls and method calls and exits, and informing
* the monitor of such events.
*/
public class BreakpointHandler implements Runnable {

private final Monitor.Controller controller;
private final PythonAdapter adapter;
private final Messenger messenger;
private final DebugpyClient debugpyClient;

public BreakpointHandler(DebugpyClient debugpyClient, PythonAdapter adapter,
Monitor.Controller controller, Messenger messenger) {
this.debugpyClient = debugpyClient;
this.adapter = adapter;
this.controller = controller;
this.messenger = messenger;
}

@Override
public void run() {
while (debugpyClient.isConnected) {
try {
messenger.breakpointEventFuture = new CompletableFuture<>();
StoppedEventClass breakpointEvent = messenger.breakpointEventFuture.get();
if (!debugpyClient.isConnected) {
return;
}
StackFrame currFrame = messenger.getCurrentFrame(Math.toIntExact(breakpointEvent.getBody().getThreadID()));
int currLineNo = (int) currFrame.getLine();
String file = currFrame.getSource().getPath();

Map<Integer, String> lineToClass = debugpyClient.fileToClassNameMap.get(file);
if (lineToClass == null || !lineToClass.containsKey(currLineNo)) {
controller.newLogMessage(this, Level.WARNING, "No mapped class for " + file + ":" + currLineNo);
debugpyClient.resume();
continue;
}

Map<Integer, BreakpointType> lineToBP = debugpyClient.fileToBreakpointTypeMap.get(file);
if (lineToBP == null || !lineToBP.containsKey(currLineNo)) {
controller.newLogMessage(this, Level.WARNING, "No breakpoint type for " + file + ":" + currLineNo);
debugpyClient.resume();
continue;
}

String qualifiedClassName = lineToClass.get(currLineNo);
BreakpointType breakpointType = lineToBP.get(currLineNo);

switch (breakpointType) {
case CONSTRUCTOR_CALL -> onConstructorCall(currFrame, qualifiedClassName);
case METHOD_CALL -> onMethodCall(currFrame, qualifiedClassName);
case METHOD_EXIT -> onMethodExit(currFrame, qualifiedClassName);
case MODIFICATION -> onAttrMod(currFrame, qualifiedClassName);
}

debugpyClient.resume(); // Match monitor running state
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
} catch (ExecutionException e) {
controller.newLogMessage(this, Level.SEVERE, "Failed to process breakpoint event: " + e.getMessage());
}
}
}

private void onConstructorCall(StackFrame currentFrame, String fullyQualifiedClassName) {
controller.newLogMessage(this, Level.FINE, "onConstructorCall: " + fullyQualifiedClassName + "." + currentFrame.getName());

PyType pyType = (PyType) controller.getVMType(fullyQualifiedClassName);
PyObject pyObject = new PyObject(adapter, messenger.getSelfId(currentFrame.getID()), pyType);

controller.onNewVMObject(pyObject);
}

private void onMethodCall(StackFrame stackFrame, String fullyQualifiedClassName) {
controller.newLogMessage(this, Level.FINE, String.format("onMethodCall: %s.%s", fullyQualifiedClassName, stackFrame.getName()));

PyType pyType = (PyType) controller.getVMType(fullyQualifiedClassName);

String methodId;
boolean isModuleMethod = pyType == null;
if (isModuleMethod) {
methodId = String.format("%s:%s.%s", fullyQualifiedClassName, fullyQualifiedClassName, stackFrame.getName());
} else {
var methods = pyType.getMethodsByName(stackFrame.getName());
if (methods.isEmpty()) {
controller.newLogMessage(this, Level.WARNING,
"No VM method found for " + fullyQualifiedClassName + "." + stackFrame.getName());
return;
}
methodId = (String) methods.getFirst().getId();
}

PyMethod pyMethod = (PyMethod) controller.getVMMethod(methodId);
if (pyMethod == null) {
controller.newLogMessage(this, Level.WARNING, "VM method lookup returned null for id: " + methodId);
return;
}

Long pyObjId = isModuleMethod
? DebugpyClient.GLOBAL_MODULE_ID
: messenger.getSelfId(stackFrame.getID());
PyObject pyObject = (PyObject) controller.getVMObject(pyObjId);

List<Value> argValues = new ArrayList<>();
for (String argName : pyMethod.getArgumentNames()) {
DAPValue argDAPValue = messenger.getMethodArgDAPValue(stackFrame.getID(), argName);
argValues.add(debugpyClient.getUSEValue(argDAPValue));
}

PyMethodCall pyMethodCall = new PyMethodCall(pyMethod, pyObject, argValues);

controller.onMethodCall(pyMethodCall);
}

private void onMethodExit(StackFrame stackFrame, String qualifiedClassName) {
PyType pyType = (PyType) controller.getVMType(qualifiedClassName);

boolean isModuleMethod = pyType == null;

String methodId;
if (isModuleMethod) {
methodId = String.format("%s:%s.%s", qualifiedClassName, qualifiedClassName, stackFrame.getName());
} else {
var methods = pyType.getMethodsByName(stackFrame.getName());
if (methods.isEmpty()) {
controller.newLogMessage(this, Level.WARNING, "No VM method found for exit of " +
qualifiedClassName + "." + stackFrame.getName());
return;
}
methodId = (String) methods.getFirst().getId();
}

PyMethod pyMethod = (PyMethod) controller.getVMMethod(methodId);
controller.onMethodExit(pyMethod, pyMethod.getId());
}

private void onAttrMod(StackFrame stackFrame, String qualifiedClassName) {
controller.newLogMessage(this, Level.FINE, "onAttributeModification: " + qualifiedClassName + "." + stackFrame.getName());

boolean isSetter = stackFrame.getName().startsWith("set");
if (!isSetter) {
controller.newLogMessage(this, Level.WARNING, "Wrongly stopped at non-setter method for attribute modification!");
return;
}

String attrName = stackFrame.getName().substring(4);

Long pyObjId = messenger.getSelfId(stackFrame.getID());
PyObject pyObject = (PyObject) controller.getVMObject(pyObjId);

PyField pyField = (PyField) pyObject.getType().getFieldByName(attrName);
if (pyField == null) {
controller.newLogMessage(this, Level.WARNING,
"Cannot determine target attribute for setter: " + stackFrame.getName());
return;
}

var methods = pyObject.getType().getMethodsByName(stackFrame.getName());
if (methods.isEmpty()) {
controller.newLogMessage(this, Level.WARNING, "No VM method found for attribute modification on " +
qualifiedClassName + "." + stackFrame.getName());
return;
}
String methodId = (String) methods.getFirst().getId();

PyMethod m = (PyMethod) controller.getVMMethod(methodId);

if (m != null) {
if (m.getArgumentNames().size() == 1) {
DAPValue argDAPValue = messenger.getMethodArgDAPValue(stackFrame.getID(), m.getArgumentNames().getFirst());
Value useValue = debugpyClient.getUSEValue(argDAPValue);
controller.onUpdateAttribute(pyObjId, pyField.getId(), useValue);
} else {
controller.newLogMessage(this, Level.WARNING,
String.format("Could not resolve new value for attribute %s! Expected 1 setter method argument. Found: %d",
pyField.getName(), m.getArgumentNames().size()));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.tzi.use.monitor.adapter.python;

public enum BreakpointType {
METHOD_CALL,
METHOD_EXIT,
CONSTRUCTOR_CALL,
MODIFICATION
}
Loading