Improved all axpect of tracing / forcing arrays

Improved ui and tree export
This commit is contained in:
Martino Ferrari
2026-05-21 06:20:22 +02:00
parent 58abf98eea
commit bceab8607c
7 changed files with 1281 additions and 328 deletions
@@ -28,6 +28,7 @@ struct DebugSignalInfo {
volatile bool isTracing;
volatile bool isForcing;
uint8 forcedValue[1024];
uint8 forcedMask[32]; // bit e set → element e is forced; supports up to 256 elements
uint32 internalID;
volatile uint32 decimationFactor;
volatile uint32 decimationCounter;
@@ -64,16 +64,34 @@ static void EscapeJson(const char8 *src, StreamString &dst) {
}
}
static bool SuffixMatch(const char8 *target, const char8 *pattern) {
uint32 tLen = StringHelper::Length(target);
uint32 pLen = StringHelper::Length(pattern);
if (pLen > tLen)
// Returns true if `str` ends with `suffix` at a word boundary (i.e. the char
// immediately before the match is '.', or the match covers the entire string).
static bool SuffixMatch(const char8 *str, const char8 *suffix) {
uint32 sLen = StringHelper::Length(str);
uint32 sufLen = StringHelper::Length(suffix);
if (sufLen > sLen)
return false;
const char8 *suffix = target + (tLen - pLen);
if (StringHelper::Compare(suffix, pattern) == 0) {
if (tLen == pLen || *(suffix - 1) == '.')
return true;
}
const char8 *tail = str + (sLen - sufLen);
if (StringHelper::Compare(tail, suffix) != 0)
return false;
return (sLen == sufLen || *(tail - 1u) == '.');
}
// Match a stored alias against a user-supplied name.
// Three cases are tried in order:
// 1. Exact match ("App.DS.Sig" == "App.DS.Sig")
// 2. Alias is longer (alias "App.DS.Sig" ends with user name "Sig")
// → user used a short / unqualified name in the command.
// 3. User name is longer (user name "App.GAM.In.Sig" ends with alias "GAM.In.Sig")
// → alias was registered with a short fallback path (FindByNameRecursive
// failed during broker init) but the client uses the full ORD path.
static bool AliasMatch(const char8 *aliasName, const char8 *userName) {
if (StringHelper::Compare(aliasName, userName) == 0)
return true;
if (SuffixMatch(aliasName, userName)) // case 2: alias ⊇ userName
return true;
if (SuffixMatch(userName, aliasName)) // case 3: userName ⊇ alias
return true;
return false;
}
@@ -286,7 +304,21 @@ void DebugServiceBase::ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
if (signalInfo == NULL_PTR(DebugSignalInfo *))
return;
if (signalInfo->isForcing) {
memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size);
uint32 nEl = signalInfo->numberOfElements;
if (nEl <= 1u) {
// Scalar — single memcpy.
memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size);
} else {
// Array — copy only the elements whose bit is set in forcedMask.
uint32 elemBytes = size / nEl;
for (uint32 e = 0u; e < nEl; e++) {
if (signalInfo->forcedMask[e >> 3u] & (uint8)(1u << (e & 7u))) {
memcpy((uint8 *)signalInfo->memoryAddress + e * elemBytes,
signalInfo->forcedValue + e * elemBytes,
elemBytes);
}
}
}
}
if (signalInfo->isTracing) {
Atomic::Add((volatile int32 *)&signalInfo->decimationCounter, 1);
@@ -352,14 +384,39 @@ void DebugServiceBase::ConsumeStepIfNeeded(const char8 *gamName,
// ---------------------------------------------------------------------------
uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) {
// Parse optional array-element suffix: "SignalName[idx]" → baseName + elemIdx.
// elemIdx == -1 means "force all elements".
const char8 *bracketPos = StringHelper::SearchChar(name, '[');
StreamString baseName;
int32 elemIdx = -1;
if (bracketPos != NULL_PTR(const char8 *)) {
uint32 baseLen = (uint32)(bracketPos - name);
(void)baseName.Write(name, baseLen);
const char8 *closePos =
StringHelper::SearchChar(bracketPos + 1u, ']');
if (closePos != NULL_PTR(const char8 *)) {
uint32 idxLen = (uint32)(closePos - bracketPos - 1u);
StreamString idxStr;
(void)idxStr.Write(bracketPos + 1u, idxLen);
uint32 parsed = 0u;
AnyType iv(UnsignedInteger32Bit, 0u, &parsed);
AnyType is_src(CharString, 0u, idxStr.Buffer());
if (TypeConvert(iv, is_src))
elemIdx = (int32)parsed;
}
} else {
baseName = name;
}
const char8 *effectiveName = baseName.Buffer();
mutex.FastLock();
uint32 count = 0u;
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == name ||
SuffixMatch(aliases[i].name.Buffer(), name)) {
DebugSignalInfo *s = signals[aliases[i].signalIndex];
uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u;
uint32 totalBytes = elemBytes * s->numberOfElements;
if (AliasMatch(aliases[i].name.Buffer(), effectiveName)) {
DebugSignalInfo *s = signals[aliases[i].signalIndex];
uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u;
uint32 totalBytes = elemBytes * s->numberOfElements;
if (elemBytes == 0u || totalBytes > (uint32)sizeof(s->forcedValue)) {
REPORT_ERROR_STATIC(
ErrorManagement::Warning,
@@ -369,9 +426,27 @@ uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) {
continue;
}
s->isForcing = true;
AnyType dest(s->type, 0u, s->forcedValue);
AnyType source(CharString, 0u, valueStr);
(void)TypeConvert(dest, source);
if (elemIdx >= 0 && (uint32)elemIdx < s->numberOfElements) {
// Force a single element — write to its slot and set its mask bit.
uint8 *dest = (uint8 *)s->forcedValue + (uint32)elemIdx * elemBytes;
AnyType dv(s->type, 0u, (void *)dest);
AnyType sv(CharString, 0u, valueStr);
(void)TypeConvert(dv, sv);
s->forcedMask[(uint32)elemIdx >> 3u] |= (uint8)(1u << ((uint32)elemIdx & 7u));
} else {
// Force all elements to the same value — convert once, replicate, set all bits.
AnyType dv(s->type, 0u, s->forcedValue);
AnyType sv(CharString, 0u, valueStr);
(void)TypeConvert(dv, sv);
for (uint32 k = 1u; k < s->numberOfElements; k++) {
memcpy(s->forcedValue + k * elemBytes, s->forcedValue, elemBytes);
}
uint32 fullBytes = s->numberOfElements / 8u;
uint32 remBits = s->numberOfElements % 8u;
memset(s->forcedMask, 0xFF, fullBytes);
if (remBits > 0u) s->forcedMask[fullBytes] = (uint8)((1u << remBits) - 1u);
}
count++;
}
}
@@ -381,12 +456,48 @@ uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) {
}
uint32 DebugServiceBase::UnforceSignal(const char8 *name) {
// Parse optional "[idx]" suffix. If present, clear only that element's bit;
// if absent, clear all bits and the isForcing flag entirely.
const char8 *bracketPos = StringHelper::SearchChar(name, '[');
StreamString baseName;
int32 elemIdx = -1;
if (bracketPos != NULL_PTR(const char8 *)) {
uint32 baseLen = (uint32)(bracketPos - name);
(void)baseName.Write(name, baseLen);
const char8 *closePos = StringHelper::SearchChar(bracketPos + 1u, ']');
if (closePos != NULL_PTR(const char8 *)) {
uint32 idxLen = (uint32)(closePos - bracketPos - 1u);
StreamString idxStr;
(void)idxStr.Write(bracketPos + 1u, idxLen);
uint32 parsed = 0u;
AnyType iv(UnsignedInteger32Bit, 0u, &parsed);
AnyType is_src(CharString, 0u, idxStr.Buffer());
if (TypeConvert(iv, is_src)) elemIdx = (int32)parsed;
}
} else {
baseName = name;
}
const char8 *effectiveName = baseName.Buffer();
mutex.FastLock();
uint32 count = 0u;
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == name ||
SuffixMatch(aliases[i].name.Buffer(), name)) {
signals[aliases[i].signalIndex]->isForcing = false;
if (AliasMatch(aliases[i].name.Buffer(), effectiveName)) {
DebugSignalInfo *s = signals[aliases[i].signalIndex];
if (elemIdx >= 0 && (uint32)elemIdx < s->numberOfElements) {
// Clear just this element's force bit.
s->forcedMask[(uint32)elemIdx >> 3u] &= (uint8)(~(1u << ((uint32)elemIdx & 7u)));
// Deactivate forcing entirely if no bits remain.
uint32 numBytes = (s->numberOfElements + 7u) / 8u;
bool anySet = false;
for (uint32 b = 0u; b < numBytes && !anySet; b++) anySet = (s->forcedMask[b] != 0u);
if (!anySet) s->isForcing = false;
} else {
// Unforce all elements.
memset(s->forcedMask, 0, sizeof(s->forcedMask));
s->isForcing = false;
}
count++;
}
}
@@ -400,8 +511,7 @@ uint32 DebugServiceBase::TraceSignal(const char8 *name, bool enable,
mutex.FastLock();
uint32 count = 0u;
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == name ||
SuffixMatch(aliases[i].name.Buffer(), name)) {
if (AliasMatch(aliases[i].name.Buffer(), name)) {
DebugSignalInfo *s = signals[aliases[i].signalIndex];
s->isTracing = enable;
s->decimationFactor = decimation;
@@ -419,8 +529,7 @@ uint32 DebugServiceBase::SetBreak(const char8 *name, uint8 op,
mutex.FastLock();
uint32 count = 0u;
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == name ||
SuffixMatch(aliases[i].name.Buffer(), name)) {
if (AliasMatch(aliases[i].name.Buffer(), name)) {
DebugSignalInfo *s = signals[aliases[i].signalIndex];
s->breakThreshold = threshold;
s->breakOp = op;
@@ -437,8 +546,7 @@ uint32 DebugServiceBase::ClearBreak(const char8 *name) {
mutex.FastLock();
uint32 count = 0u;
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == name ||
SuffixMatch(aliases[i].name.Buffer(), name)) {
if (AliasMatch(aliases[i].name.Buffer(), name)) {
signals[aliases[i].signalIndex]->breakOp = BREAK_OFF;
count++;
}
@@ -454,8 +562,7 @@ bool DebugServiceBase::IsInstrumented(const char8 *fullPath, bool &traceable,
mutex.FastLock();
bool found = false;
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == fullPath ||
SuffixMatch(aliases[i].name.Buffer(), fullPath)) {
if (AliasMatch(aliases[i].name.Buffer(), fullPath)) {
found = true;
break;
}
@@ -517,8 +624,7 @@ uint32 DebugServiceBase::RegisterMonitorSignal(const char8 *path,
m.internalID = 0x80000000u | monitoredSignals.GetNumberOfElements();
// Re-use existing brokered signal ID if available
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == path ||
SuffixMatch(aliases[i].name.Buffer(), path)) {
if (AliasMatch(aliases[i].name.Buffer(), path)) {
m.internalID = signals[aliases[i].signalIndex]->internalID;
break;
}
@@ -652,8 +758,7 @@ void DebugServiceBase::GetSignalValue(const char8 *name, StreamString &out) {
mutex.FastLock();
DebugSignalInfo *sig = NULL_PTR(DebugSignalInfo *);
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == name ||
SuffixMatch(aliases[i].name.Buffer(), name)) {
if (AliasMatch(aliases[i].name.Buffer(), name)) {
sig = signals[aliases[i].signalIndex];
break;
}
@@ -809,14 +914,156 @@ void DebugServiceBase::BuildDiscoverCache() {
}
void DebugServiceBase::BuildTreeCache() {
treeCacheValid = false;
treeCache = "";
treeCache += "{\"Name\":\"Root\",\"Class\":\"ObjectRegistryDatabase\","
"\"Children\":[\n";
(void)ExportTree(ObjectRegistryDatabase::Instance(), treeCache,
NULL_PTR(const char8 *));
treeCache += "\n]}\nOK TREE\n";
treeCacheValid = true;
// No-op: tree is now served per-node via ExportTreeNode.
}
// ---------------------------------------------------------------------------
// ExportTreeNode — serve one level of the object tree
// ---------------------------------------------------------------------------
void DebugServiceBase::ExportTreeNode(const char8 *path, StreamString &out) {
bool isRoot =
(path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0);
out += "{\"Path\":\"";
EscapeJson(isRoot ? "" : path, out);
out += "\",\"Children\":[\n";
uint32 count = 0u;
ReferenceContainer *container = NULL_PTR(ReferenceContainer *);
DataSourceI * ds = NULL_PTR(DataSourceI *);
GAM * gam = NULL_PTR(GAM *);
if (isRoot) {
container = ObjectRegistryDatabase::Instance();
} else {
Reference ref = ObjectRegistryDatabase::Instance()->Find(path);
if (ref.IsValid()) {
container = dynamic_cast<ReferenceContainer *>(ref.operator->());
ds = dynamic_cast<DataSourceI *>(ref.operator->());
gam = dynamic_cast<GAM *>(ref.operator->());
}
}
// ReferenceContainer children
if (container != NULL_PTR(ReferenceContainer *)) {
uint32 n = container->Size();
for (uint32 i = 0u; i < n; i++) {
Reference child = container->Get(i);
if (!child.IsValid())
continue;
const char8 *cname = child->GetName();
if (cname == NULL_PTR(const char8 *))
cname = "unnamed";
ReferenceContainer *irc =
dynamic_cast<ReferenceContainer *>(child.operator->());
DataSourceI *ids = dynamic_cast<DataSourceI *>(child.operator->());
GAM * igam = dynamic_cast<GAM *>(child.operator->());
bool hasCh =
(irc != NULL_PTR(ReferenceContainer *) && irc->Size() > 0u) ||
ids != NULL_PTR(DataSourceI *) || igam != NULL_PTR(GAM *);
if (count > 0u)
out += ",\n";
out += "{\"Name\":\"";
EscapeJson(cname, out);
out += "\",\"Class\":\"";
EscapeJson(child->GetClassProperties()->GetName(), out);
out.Printf("\",\"HasChildren\":%s}", hasCh ? "true" : "false");
count++;
}
}
// DataSourceI signals
if (ds != NULL_PTR(DataSourceI *)) {
uint32 ns = ds->GetNumberOfSignals();
for (uint32 j = 0u; j < ns; j++) {
StreamString sn;
(void)ds->GetSignalName(j, sn);
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(
ds->GetSignalType(j));
uint8 d = 0u;
(void)ds->GetSignalNumberOfDimensions(j, d);
uint32 el = 0u;
(void)ds->GetSignalNumberOfElements(j, el);
StreamString sfp;
sfp.Printf("%s.%s", path, sn.Buffer());
bool tr = false, fo = false;
(void)IsInstrumented(sfp.Buffer(), tr, fo);
if (count > 0u)
out += ",\n";
out += "{\"Name\":\"";
EscapeJson(sn.Buffer(), out);
out += "\",\"Class\":\"Signal\",\"Type\":\"";
EscapeJson(st ? st : "Unknown", out);
out.Printf("\",\"Dimensions\":%u,\"Elements\":%u,"
"\"IsTraceable\":%s,\"IsForcable\":%s,\"HasChildren\":false}",
d, el, tr ? "true" : "false", fo ? "true" : "false");
count++;
}
}
// GAM input signals
if (gam != NULL_PTR(GAM *)) {
uint32 nIn = gam->GetNumberOfInputSignals();
for (uint32 j = 0u; j < nIn; j++) {
StreamString sn;
(void)gam->GetSignalName(InputSignals, j, sn);
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(
gam->GetSignalType(InputSignals, j));
uint32 d = 0u;
(void)gam->GetSignalNumberOfDimensions(InputSignals, j, d);
uint32 el = 0u;
(void)gam->GetSignalNumberOfElements(InputSignals, j, el);
StreamString sfp;
sfp.Printf("%s.In.%s", path, sn.Buffer());
bool tr = false, fo = false;
(void)IsInstrumented(sfp.Buffer(), tr, fo);
if (count > 0u)
out += ",\n";
out += "{\"Name\":\"In.";
EscapeJson(sn.Buffer(), out);
out += "\",\"Class\":\"InputSignal\",\"Type\":\"";
EscapeJson(st ? st : "Unknown", out);
out.Printf("\",\"Dimensions\":%u,\"Elements\":%u,"
"\"IsTraceable\":%s,\"IsForcable\":%s,\"HasChildren\":false}",
d, el, tr ? "true" : "false", fo ? "true" : "false");
count++;
}
uint32 nOut = gam->GetNumberOfOutputSignals();
for (uint32 j = 0u; j < nOut; j++) {
StreamString sn;
(void)gam->GetSignalName(OutputSignals, j, sn);
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(
gam->GetSignalType(OutputSignals, j));
uint32 d = 0u;
(void)gam->GetSignalNumberOfDimensions(OutputSignals, j, d);
uint32 el = 0u;
(void)gam->GetSignalNumberOfElements(OutputSignals, j, el);
StreamString sfp;
sfp.Printf("%s.Out.%s", path, sn.Buffer());
bool tr = false, fo = false;
(void)IsInstrumented(sfp.Buffer(), tr, fo);
if (count > 0u)
out += ",\n";
out += "{\"Name\":\"Out.";
EscapeJson(sn.Buffer(), out);
out += "\",\"Class\":\"OutputSignal\",\"Type\":\"";
EscapeJson(st ? st : "Unknown", out);
out.Printf("\",\"Dimensions\":%u,\"Elements\":%u,"
"\"IsTraceable\":%s,\"IsForcable\":%s,\"HasChildren\":false}",
d, el, tr ? "true" : "false", fo ? "true" : "false");
count++;
}
}
out += "\n]}\nOK TREE\n";
}
void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) {
@@ -857,8 +1104,7 @@ void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) {
mutex.FastLock();
bool found = false;
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
if (aliases[i].name == path ||
SuffixMatch(aliases[i].name.Buffer(), path)) {
if (AliasMatch(aliases[i].name.Buffer(), path)) {
DebugSignalInfo *s = signals[aliases[i].signalIndex];
const char8 *tn =
TypeDescriptor::GetTypeNameFromTypeDescriptor(s->type);
@@ -922,7 +1168,6 @@ void DebugServiceBase::SetFullConfig(ConfigurationDatabase &config) {
config.Copy(fullConfig);
manualConfigSet = true;
BuildDiscoverCache();
BuildTreeCache();
}
void DebugServiceBase::RebuildConfigFromRegistry() {
@@ -1289,9 +1534,10 @@ void DebugServiceBase::HandleCommand(const StreamString &cmdIn,
} else if (token == "DISCOVER") {
Discover(out);
} else if (token == "TREE") {
if (!treeCacheValid)
BuildTreeCache();
out += treeCache;
StreamString path;
(void)cmd.GetToken(path, delims, term);
ExportTreeNode(path.Size() > 0u ? path.Buffer() : NULL_PTR(const char8 *),
out);
} else if (token == "INFO") {
StreamString path;
if (cmd.GetToken(path, delims, term))
@@ -176,6 +176,7 @@ protected:
uint32 ExportTree(ReferenceContainer *container, StreamString &json,
const char8 *pathPrefix);
void ExportTreeNode(const char8 *path, StreamString &out);
void EnrichWithConfig(const char8 *path, StreamString &json);
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);