Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Emit debugging info for visualiser to use #199

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
36 changes: 30 additions & 6 deletions src/benchmark/Sieve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
App,
TimeValue,
Origin,
Log
Log,
GraphDebugLogger
} from "../core/internal";

Log.global.level = Log.levels.INFO;
Expand All @@ -21,7 +22,7 @@
value = new OutPort<number>(this);

constructor(parent: Reactor, until = 100000, period: TimeValue) {
super(parent);
super(parent, "Ramp");
this.until = new Parameter(until);
this.next = new Action<number>(this, Origin.logical, period);
this.addReaction(
Expand Down Expand Up @@ -56,7 +57,7 @@
hasChild: State<boolean>;

constructor(parent: Reactor, startPrime: number, numberOfPrimes: number) {
super(parent);
super(parent, `FilterFor${startPrime}`);
// console.log("Created filter with prime: " + prime)
this.startPrime = new Parameter(startPrime);
this.localPrimes = new State(new Array<number>());
Expand Down Expand Up @@ -87,17 +88,24 @@
if (size < numberOfPrimes) {
seen.push(p);
console.log(`Found new prime number ${p}`);
if (!primes.has(p)) {
;
} else {
primes.delete(p);
}
} else {
// Potential prime found.
if (!hasChild.get()) {
const n = new Filter(this.getReactor(), p, numberOfPrimes);
const n = this.getReactor()._uncheckedAddSibling(Filter, p, numberOfPrimes);
// this.start(n)
// console.log("CREATING...")
// let x = this.create(Filter, [this.getReactor(), p])
// console.log("CREATED: " + x._getFullyQualifiedName())
// FIXME: weird hack. Maybe just accept writable ports as well?
const port = (out as unknown as WritablePort<number>).getPort();
console.log("connecting......");
this.connect(port, n.inp);
printSieveGraph();
// FIXME: this updates the dependency graph, but it doesn't redo the topological sort
// For a pipeline like this one, it is not necessary, but in general it is.
// Can we avoid redoing the entire sort?
Expand Down Expand Up @@ -126,12 +134,28 @@
success?: () => void,
fail?: () => void
) {
super(timeout, keepAlive, fast, success, fail);
super(timeout, keepAlive, fast, success, fail, name);
this.source = new Ramp(this, 100000, TimeValue.nsec(1));
this.filter = new Filter(this, 2, 1000);
this._connect(this.source.value, this.filter.inp);
}
}

const sieve = new Sieve("Sieve");

const sieve = new Sieve("Sieve", undefined, undefined, undefined, ()=>{globalThis.graphDebugLogger?.write("debug0.json")});

const printSieveGraph = (): void => {
const graph = sieve["_getPrecedenceGraph"]();

Check failure on line 148 in src/benchmark/Sieve.ts

View workflow job for this annotation

GitHub Actions / lint

["_getPrecedenceGraph"] is better written in dot notation
const hierarchy = sieve._getNodeHierarchyLevels();
const str = graph.toMermaidString(undefined, hierarchy);
const time = sieve["util"].getElapsedLogicalTime();

Check failure on line 151 in src/benchmark/Sieve.ts

View workflow job for this annotation

GitHub Actions / lint

["util"] is better written in dot notation
console.log(str);
console.log(time);
}

globalThis.graphDebugLogger = new GraphDebugLogger(sieve);
globalThis.recording = false;

const primes = new Set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 9972, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]);

sieve._start();
11 changes: 5 additions & 6 deletions src/core/component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {Runtime} from "./internal";
import {Reactor, App, MultiPort, IOPort, Bank} from "./internal";
import {v4 as uuidv4} from "uuid";

/**
* Base class for named objects embedded in a hierarchy of reactors. Each
Expand All @@ -17,7 +18,7 @@ export abstract class Component {
* A symbol that identifies this component, and it also used to selectively
* grant access to its privileged functions.
*/
protected _key = Symbol("Unique component identifier");
protected _key = Symbol(uuidv4());

/**
* The container of this component. Each component is contained by a
Expand Down Expand Up @@ -186,11 +187,9 @@ export abstract class Component {
public _getName(): string {
let name;

if (this instanceof App) {
if (this instanceof Reactor) {
name = this._name;
} else {
name = Component.keyOfMatchingEntry(this, this._container);
}
}

if (name === "" && this instanceof IOPort) {
name = Component.keyOfMatchingMultiport(this, this._container);
Expand All @@ -200,7 +199,7 @@ export abstract class Component {
name = Component.keyOfMatchingBank(this, this._container);
}

if (name !== "") {
if (name != null && name !== "") {
return name;
} else {
return this.constructor.name;
Expand Down
82 changes: 73 additions & 9 deletions src/core/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,56 @@
* @author Marten Lohstroh <[email protected]>
*/

import { DebugLogger } from "util";

Check failure on line 6 in src/core/graph.ts

View workflow job for this annotation

GitHub Actions / lint

'DebugLogger' is defined but never used
import {Reaction} from "./reaction";
import type {Sortable, Variable} from "./types";
import {Log} from "./util";
import {GraphDebugLogger, Log} from "./util";

Check failure on line 9 in src/core/graph.ts

View workflow job for this annotation

GitHub Actions / lint

Import "GraphDebugLogger" is only used as types

// TODO: find a way to to this with decorators.
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-explicit-any
/* const debugLoggerDecorator = (target: any, context: ClassMethodDecoratorContext) => {
if (context.kind === "method") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function (this: any, ...args: unknown[]): any {
console.log(`${context.name.toString()} is called.`);
console.log(`Tracestack: ${(new Error()).stack}`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
return target.call(this, ...args);
}
}
} */

declare global {
// eslint-disable-next-line no-var
var graphDebugLogger: GraphDebugLogger | undefined;
// eslint-disable-next-line no-var
var recording: boolean;
}

const debugHelper = (stacktrace: Error): void => {
if (globalThis.recording) { return; }
// If recording now, do not record any subsequent operations,
// as recursion hell might involve when calling `capture`,
// causing infinite loop.
globalThis.recording = true;
if (globalThis.graphDebugLogger == null) {
return
}

const debuglogger = globalThis.graphDebugLogger;
debuglogger.capture(stacktrace);
globalThis.recording = false;
};

/**
* A generic precedence graph.
*/

export interface HierarchyGraphLevel<T> {
name: string,
nodes: T[],
childrenLevels: Array<HierarchyGraphLevel<T>>,
}
export class PrecedenceGraph<T> {
/**
* A map from nodes to the set of their upstream neighbors.
Expand Down Expand Up @@ -47,6 +90,7 @@
* @param node
*/
addNode(node: T): void {
debugHelper(new Error("addNode"));
if (!this.adjacencyMap.has(node)) {
this.adjacencyMap.set(node, new Set());
}
Expand Down Expand Up @@ -137,6 +181,7 @@
* @param downstream The node at which the directed edge ends.
*/
addEdge(upstream: T, downstream: T): void {
debugHelper(new Error("addEdge"));
const deps = this.adjacencyMap.get(downstream);
if (deps == null) {
this.adjacencyMap.set(downstream, new Set([upstream]));
Expand Down Expand Up @@ -189,10 +234,10 @@
* @param edgesWithIssue An array containing arrays with [origin, effect].
* Denotes edges in the graph that causes issues to the execution, will be visualized as `--x` in mermaid.
*/
toMermaidString(edgesWithIssue?: Array<[T, T]>): string {
toMermaidString(edgesWithIssue?: Array<[T, T]>, hierarchy?: HierarchyGraphLevel<T>, uniqueNames?: boolean): string {
if (edgesWithIssue == null) edgesWithIssue = [];
let result = "graph";
const nodeToNumber = new Map<T, number>();
let result = "graph\n";
const nodeToSymbolString = new Map<T, string>();
const getNodeString = (node: T, def: string): string => {
if (node == null || node?.toString === Object.prototype.toString) {
console.error(
Expand All @@ -205,27 +250,46 @@
return node.toString();
};

// Build a block here since we only need `counter` temporarily here
if (hierarchy != null) {
let counter = 0;
let subgraphCounter = 0;
const recurse = (h: HierarchyGraphLevel<T>, level: number): void => {
const indent = " ".repeat(level);
result += level === 0 ? "" : `${indent}subgraph "${(uniqueNames ?? false) ? h.name : `sg${subgraphCounter++}`}"\n`;
for (const v of h.nodes) {
result += `${indent} ${counter}["${getNodeString(v, String(counter))}"]\n`
nodeToSymbolString.set(v, `${counter++}`);
}
for (const c of h.childrenLevels) {
recurse(c, level + 1);
}
result += level === 0 ? "" : `${indent}end\n`;
}

recurse(hierarchy, 0);
}

// Build a block here since we only need `counter` temporarily here
// We use numbers instead of names of reactors directly as node names
// in mermaid.js because mermaid has strict restrictions regarding
// what could be used as names of the node.
{
let counter = 0;
for (const v of this.getNodes()) {
result += `\n${counter}["${getNodeString(v, String(counter))}"]`;
nodeToNumber.set(v, counter++);
if (nodeToSymbolString.has(v)) { continue; }
result += `\nmissing${counter}["${getNodeString(v, String(counter))}"]`;
nodeToSymbolString.set(v, `missing${counter++}`);
}
}
// This is the effect
for (const s of this.getNodes()) {
// This is the origin
for (const t of this.getUpstreamNeighbors(s)) {
result += `\n${nodeToNumber.get(t)}`;
result += `\n${nodeToSymbolString.get(t)}`;
result += edgesWithIssue.some((v) => v[0] === t && v[1] === s)
? " --x "
: " --> ";
result += `${nodeToNumber.get(s)}`;
result += `${nodeToSymbolString.get(s)}`;
}
}
return result;
Expand Down
4 changes: 4 additions & 0 deletions src/core/port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ export abstract class IOPort<T> extends Port<T> {
}
}

public checkKey(key: symbol | undefined): boolean {
return this._key === key;
}

/**
* Only the holder of the key may obtain a writable port.
* @param key
Expand Down
20 changes: 11 additions & 9 deletions src/core/reaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export class Reaction<T extends Variable[]>
private deadline?: TimeValue,
private readonly late: (...args: ArgList<T>) => void = () => {
Log.global.warn("Deadline violation occurred!");
}
},
readonly name?: string
) {}

/**
Expand Down Expand Up @@ -181,9 +182,9 @@ export class Reaction<T extends Variable[]>
* Return string representation of the reaction.
*/
public toString(): string {
return `${this.reactor._getFullyQualifiedName()}[R${this.reactor._getReactionIndex(
this as unknown as Reaction<Variable[]>
)}]`;
return `${this.reactor._getFullyQualifiedName()}` +
((this.name != null) ? `${this.name} aka` : "") +
`[R${this.reactor._getReactionIndex(this as unknown as Reaction<Variable[]>)}]`;
}
}

Expand All @@ -199,18 +200,19 @@ export class Mutation<T extends Variable[]> extends Reaction<T> {
args: [...ArgList<T>],
react: (...args: ArgList<T>) => void,
deadline?: TimeValue,
late?: (...args: ArgList<T>) => void
late?: (...args: ArgList<T>) => void,
name?: string
) {
super(__parent__, sandbox, trigs, args, react, deadline, late);
super(__parent__, sandbox, trigs, args, react, deadline, late, name);
this.parent = __parent__;
}

/**
* @override
*/
public toString(): string {
return `${this.parent._getFullyQualifiedName()}[M${this.parent._getReactionIndex(
this as unknown as Reaction<Variable[]>
)}]`;
return `${this.parent._getFullyQualifiedName()}` +
((this.name != null) ? `${this.name} aka` : "") +
`[M${this.parent._getReactionIndex(this as unknown as Reaction<Variable[]>)}]`;
}
}
Loading
Loading