2.4 Node path patterns 2.6 Event system
Analyzer User's Guide  /  2 OS Awareness  / 

2.5 Scripting

The OS Awareness framework supports scripting against the node tree. The osa_node_tree_query interface implemented by the os_awareness object can be used for retrieving the current state of the node tree. The osa_node_tree_notification interface, also implemented by the os_awareness object, can be used to register callbacks for notifications about changes to the node tree.

The administrator object can be retrieved by calling the get_admin function in the osa_component interface, implemented by the OSA object. This interface also provides the get_root_node function. This gives a known node ID which can be used to access the root node and its descendant nodes.

Details about the interfaces used for scripting can be found in the API - Reference Manual.

The following Python example script uses the OS Awareness framework and the Linux tracker to track a process and count all hardware exceptions that happen while that process is active. The basic idea is to activate the Core_Exception hap only when the given program is activate. It assumes that the target system is named board.

import simics
import conf
class exception_counter:
    "This class counts hardware exceptions for a specific process."

    def __init__(self, software_comp, process_name):
        # Get the osa_admin object from the component, this will be used to
        # access the node tree interfaces.
        self.osa_admin = software_comp.iface.osa_component.get_admin()
        self.notifiers = set()
        self.exc_haps = {}

        self.exceptions = {}          # The result

        # Most OSA interface functions require a node ID. Retrieve the root_id
        # from the component. Using the root ID in combination with the
        # recursive flag makes it possible to get notifications for the entire
        # node tree.
        root_node = software_comp.iface.osa_component.get_root_node()
        if not root_node.valid:
            print ("No root node present")
            return
        self.root_id = root_node.id

        # The node names will be truncated to 15 characters, since
        # they use the Linux task 'comm' field. So we only match the
        # first 15 characters of the requested process name.
        process_name = process_name[:15]

        # Get interface used to register callback functions for node tree
        # updates.
        self.notification_ifc = self.osa_admin.iface.osa_node_tree_notification
        # Get interface used to query the current state in the node tree.
        self.query_ifc = self.osa_admin.iface.osa_node_tree_query

        # Install a callback on node creation.
        cid = self.notification_ifc.notify_create(self.root_id, True,
                                                  self.create_cb, process_name)
        self.notifiers.add(cid)

        # Install a callback on changes to the 'name' property in any node, in
        # case the program switches name after the node was created.
        self.notifiers.add(
            self.notification_ifc.notify_property_change(
                self.root_id, "name", True, self.name_cb,
                process_name))

        print(("Will count exceptions for the next process called %s"
               % process_name))

    def is_process(self, node_id):
        # This will only work for the Linux tracker. It uses the fact that a
        # process node contains the process id, but not the thread id.
        props = self.query_ifc.get_node(node_id)
        return 'pid' in props and not 'tid' in props

    def create_cb(self, process_name, osa_admin, curcpu, node_id):
        # There can be other nodes than the process node with a
        # matching name, for example thread nodes. Verify both name
        # and that it is a process.
        if (self.query_ifc.get_node(node_id)['name'] == process_name
            and self.is_process(node_id)):
            self.process_found(node_id)

    def name_cb(self, process_name, osa_admin, curcpu, node_id,
                key, old_val, new_val):
        # There can be other nodes than the process node with a
        # matching name, for example thread nodes. Verify both name
        # and that it is a process.
        if new_val == process_name and self.is_process(node_id):
            self.process_found(node_id)

    def process_found(self, node_id):
        # Remove the callbacks for node creation and name changes.
        while self.notifiers:
            self.notification_ifc.cancel_notify(self.notifiers.pop())

        # Install callbacks when processors enter and leave this
        # process node.
        self.notifiers.add(
            self.notification_ifc.notify_cpu_move_to(node_id, self.move_to_cb,
                                                     None))
        self.notifiers.add(
            self.notification_ifc.notify_cpu_move_from(node_id,
                                                       self.move_from_cb, None))

        # Install a callback when the process finishes.
        self.notifiers.add(
            self.notification_ifc.notify_destroy(node_id, False,
                                                 self.destroy_cb, None))

        # For each CPU already executing in this node, make sure
        # to enable counting.
        for cpu in self.query_ifc.get_current_processors(node_id):
            self.enable_counting(cpu)

    def enable_counting(self, cpu):
        # Install a hap callback for the exception hap.
        self.exc_haps[cpu] = simics.SIM_hap_add_callback_obj(
            "Core_Exception", cpu, 0, self.exception_cb, None)

    def disable_counting(self, cpu):
        simics.SIM_hap_delete_callback_id("Core_Exception", self.exc_haps[cpu])

    def move_to_cb(self, data, osa_admin, cpu, node_path):
        self.enable_counting(cpu)

    def move_from_cb(self, data, osa_admin, cpu, node_path):
        self.disable_counting(cpu)

    def destroy_cb(self, data, osa_admin, cpu, node_id):
        print("The process finished")
        for exc in sorted(self.exceptions.keys()):
            print("%5d %-30s: %8d" % (exc, cpu.iface.exception.get_name(exc),
                                      self.exceptions[exc]))
        while self.notifiers:
            self.notification_ifc.cancel_notify(self.notifiers.pop())

    def exception_cb(self, data, cpu, exception):
        if exception in self.exceptions:
            self.exceptions[exception] += 1
        else:
            self.exceptions[exception] = 1

counter = exception_counter(conf.board.software, "ls")

Remember that this will only work if the tracker is enabled. It can be enabled with the enable-tracker command.

2.4 Node path patterns 2.6 Event system