Skip to content

Commit d1b5c06

Browse files
Add create-field-plots command to generate and post XML analyses via TSP
Adds a CLI command and MCP tool that: - Takes a dict of series names to (event, field) pairs - Generates a Trace Compass XML analysis with stateProvider and xyView - Posts it to the server via the configuration API - Is destructive-idempotent (deletes existing config before re-posting)
1 parent 21a2740 commit d1b5c06

2 files changed

Lines changed: 92 additions & 2 deletions

File tree

tmll/mcp/cli.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,78 @@ def list_experiments(args):
251251
print(f"{exp.name} - {exp.UUID}")
252252

253253

254+
def create_field_plots(args):
255+
"""Generate XML analysis for field plots and post it to the trace server."""
256+
import tempfile
257+
import os
258+
259+
client = TMLLClient(args.host, args.port, verbose=args.verbose)
260+
261+
# Parse the series spec: JSON dict of {series_name: [[event, field], ...]}
262+
series_spec = json.loads(args.series)
263+
264+
# Build XML: state provider stores fields under series_name/*,
265+
# xyView uses entry path="series_name/*" to plot all fields in that series.
266+
analysis_id = f"org.eclipse.tracecompass.tmll.field.{args.analysis_name}"
267+
handlers = []
268+
xy_views = []
269+
270+
for series_name, event_fields in series_spec.items():
271+
for event_name, field_name in event_fields:
272+
handlers.append(
273+
f' <eventHandler eventName="{event_name}">\n'
274+
f' <stateChange>\n'
275+
f' <stateAttribute type="constant" value="{series_name}"/>\n'
276+
f' <stateAttribute type="constant" value="{event_name}.{field_name}"/>\n'
277+
f' <stateValue type="eventField" value="{field_name}"/>\n'
278+
f' </stateChange>\n'
279+
f' </eventHandler>'
280+
)
281+
xy_views.append(
282+
f' <xyView id="{analysis_id}.{series_name}.xy">\n'
283+
f' <head>\n'
284+
f' <analysis id="{analysis_id}"/>\n'
285+
f' <label value="{series_name}"/>\n'
286+
f' </head>\n'
287+
f' <entry path="{series_name}/*">\n'
288+
f' <display type="self"/>\n'
289+
f' </entry>\n'
290+
f' </xyView>'
291+
)
292+
293+
xml = (
294+
'<?xml version="1.0" encoding="UTF-8"?>\n'
295+
'<tmfxml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n'
296+
' xsi:noNamespaceSchemaLocation="xmlDefinition.xsd">\n'
297+
f' <stateProvider id="{analysis_id}" version="1">\n'
298+
f' <head>\n'
299+
f' <label value="{args.analysis_name}"/>\n'
300+
f' </head>\n'
301+
+ '\n'.join(handlers) + '\n'
302+
f' </stateProvider>\n'
303+
+ '\n'.join(xy_views) + '\n'
304+
'</tmfxml>\n'
305+
)
306+
307+
# Write to file and post (delete first for idempotency)
308+
xml_path = os.path.join(tempfile.gettempdir(), f"{args.analysis_name}.xml")
309+
with open(xml_path, 'w') as f:
310+
f.write(xml)
311+
312+
config_type = 'org.eclipse.tracecompass.tmf.core.config.xmlsourcetype'
313+
config_id = f"{args.analysis_name}.xml"
314+
client.tsp_client.delete_configuration(config_type, config_id)
315+
316+
response = client.tsp_client.post_configuration(config_type, {'path': xml_path})
317+
318+
if response.status_code == 200:
319+
print(f"Posted analysis '{args.analysis_name}' (file: {xml_path})")
320+
print(f"Analysis ID: {analysis_id}")
321+
print(f"XY View ID: {analysis_id}.xy")
322+
else:
323+
print(f"Failed to post analysis: {response.status_code} {response.status_text}")
324+
325+
254326
def delete_experiment(args):
255327
"""Delete an experiment"""
256328
client = TMLLClient(args.host, args.port, verbose=args.verbose)
@@ -291,6 +363,12 @@ def main():
291363
fetch_parser.add_argument("-o", "--output", help="Output file prefix")
292364
fetch_parser.set_defaults(func=fetch_data_cmd)
293365

366+
# create-field-plots command
367+
field_plots_parser = subparsers.add_parser("create-field-plots", help="Generate and post XML analysis for field plots")
368+
field_plots_parser.add_argument("analysis_name", help="Unique name for the analysis")
369+
field_plots_parser.add_argument("series", help='JSON: {"series_name": [["event", "field"], ...], ...}')
370+
field_plots_parser.set_defaults(func=create_field_plots)
371+
294372
# delete command
295373
delete_parser = subparsers.add_parser("delete", help="Delete an experiment")
296374
delete_parser.add_argument("experiment", help="Experiment UUID")

tmll/mcp/server.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,6 @@ def get_experiment_resource(experiment_id: str) -> str:
359359
return json.dumps({"name": name, "uuid": uid})
360360
return json.dumps({"error": f"Experiment {experiment_id} not found"})
361361

362-
363-
364362
# Override list_resources to dynamically expose each open experiment as a resource.
365363
_original_list_resources = mcp.list_resources
366364

@@ -396,5 +394,19 @@ async def _dynamic_list_resources():
396394
mcp._mcp_server.list_resources()(_dynamic_list_resources)
397395

398396

397+
@mcp.tool()
398+
def create_field_plots(analysis_name: str, series: dict[str, list[list[str]]], host: Optional[str] = None, port: Optional[int] = None) -> str:
399+
"""Generate an XML analysis to plot event fields and post it to the trace server.
400+
401+
Args:
402+
analysis_name: Unique name for the analysis
403+
series: Dict mapping series names to lists of [event_name, field_name] pairs.
404+
Example: {"cpu_prio": [["sched_switch", "prev_prio"]], "mem": [["kmem_alloc", "bytes_alloc"]]}
405+
"""
406+
import json as _json
407+
series_json = _json.dumps(series)
408+
return run_cli(*_global_args(host, port), "create-field-plots", analysis_name, series_json)
409+
410+
399411
if __name__ == "__main__":
400412
mcp.run()

0 commit comments

Comments
 (0)