Instrumenting *nix Systems via C#, C++, Mono and Qt

Hi Community,

The following article is about one of the subjects I’m most passionate about that’s integration of disparate systems and cross-platform computing. I think, it’s something I had to publish and it’s one of the core components of my SIF project (Service Integration Framework). This approach can be applied to a variety of scenarios, like exposing performance counters on a *nix system that is hosted in the cloud (good example, a NAT server hosted AWSLinux server without any UI elements but only allowing connection over HTTP/HTTPS/SSH). This also reminds me of a distributed CyberKiosk (Cyber Cafe) solution I built in Visual FoxPro like 20 years ago, unfortunately back then SOAP was pretty much unknown territory thus management and instrumentation of the networked computers was achievable through sockets only (and yes, I did try DCOM but back then it gave me more headaches than solutions).

Anyways, I’m done with the introduction so what’s this article about? Well,  in a nutshell it’s part of my SIF project but it’s also also a PoC (Proof of Concept) of a Qt application hosting the CLR implemented in mono which in turn exposes a WCF service that allows me to instrument the target *nix system through SOAP, sounds cool, right? *nix systems as their Windows counterparts provide a variety of different performance counters and profiling tools, a good example can be the perf stat utility

angel@Obi-wan:~$ sudo perf stat -a

^C

 Performance counter stats for 'system wide':

 

      62568.846961      task-clock (msec)         #    4.000 CPUs utilized            (100.00%)

           214,730      context-switches          #    0.003 M/sec                    (100.00%)

            11,889      cpu-migrations            #    0.190 K/sec                    (100.00%)

             2,019      page-faults               #    0.032 K/sec                  

    28,653,473,022      cycles                    #    0.458 GHz                      (100.00%)

    39,173,539,651      stalled-cycles-frontend   #  136.71% frontend cycles idle     (100.00%)

   <not supported>      stalled-cycles-backend   

     9,286,112,645      instructions              #    0.32  insns per cycle        

                                                  #    4.22  stalled cycles per insn  (100.00%)

     1,977,739,115      branches                  #   31.609 M/sec                    (100.00%)

        83,956,096      branch-misses             #    4.25% of all branches        

 

      15.640948007 seconds time elapsed

 

 

angel@Obi-wan:~$ 

 

 

In order to understand how this solution was architected is important to mention that our Qt application hosts the CLR (more information on embedding mono here), once the AppDomain is created we pass a native functor to the managed world in order to bubble up messages back to the Qt application. C++11 introduced the std::function class template that’s passed to the CLR as a pointer and marshalled by our C# code, but I think it’s worthy to start from the very beginning and then we’ll describe this later.

 

Our Qt application implements a wrapper class (monowrapper) to interact with mono

 

#include "monowrapper.h"

 

monowrapper::monowrapper() {

 

}

 

bool monowrapper::Initialized_get() const {

    return m_IsInitialized;

}

 

void monowrapper::UnloadAppDomain() {

    if (m_IsInitialized)

        mono_jit_cleanup(m_domain);

}

 

void monowrapper::CreateDomain(std::function<void(const char*)> logger) {

    if (m_IsInitialized)

        return;

 

    m_logger = logger;

 

    mono_set_dirs("/usr/lib/", "/etc/mono");

 

    mono_config_parse(nullptr);

 

    m_domain = mono_jit_init("./MonoDaemon.Service.dll");

 

    m_assembly = mono_domain_assembly_open (m_domain, "./MonoDaemon.ServiceHost.dll");

 

    if (m_assembly) {

        MonoObject* ex = nullptr;

        mono_domain_set_config(m_domain, "/etc/mono/4.5/", "machine.config");

        m_monoImage = mono_assembly_get_image(m_assembly);

        m_hostClass = mono_class_from_name(m_monoImage, "MonoDaemon", "ServiceHost");

        m_hostInstance  = mono_object_new(m_domain, m_hostClass);

        auto ctorMethod = mono_class_get_method_from_name(m_hostClass, ".ctor", 0);

        mono_runtime_invoke(ctorMethod, m_hostClass, nullptr, &ex);

        auto hostInitializeMethod = mono_class_get_method_from_name(m_hostClass, "InitializeHost", 1);

        void* args[1] = {&logger};

        mono_runtime_invoke(hostInitializeMethod, m_hostInstance, args, &ex);

        m_IsInitialized = true;

    } else {

        // Log & display error message here

 

    }

}

 

The mainwindow class has an instance of the monowrapper class

#include "mainwindow.h"

#include "ui_mainwindow.h"

 

static const MainWindow* m_selfReference;

 

MainWindow::MainWindow(QWidget *parent) :

    QMainWindow(parent),

    ui(new Ui::MainWindow)

{

    ui->setupUi(this);

    m_monoWrapper = std::unique_ptr<monowrapper>(new monowrapper);

    m_selfReference = this;

}

 

MainWindow::~MainWindow()

{

    delete ui;

}

 

void MainWindow::LogMessage(const char *msg) {

    auto msgAsStr = std::string(msg);

    auto lstMessages = m_selfReference->findChild<QListWidget*>("lstMessages");

    new QListWidgetItem(tr(msgAsStr.c_str()), lstMessages);

}

 

void MainWindow::on_btnStartHost_clicked()

{

    std::function<void(const char*)> func =  &MainWindow::LogMessage;

    m_monoWrapper->CreateDomain(func);

}

 

void MainWindow::on_btnExitHost_clicked()

{

    m_monoWrapper->UnloadAppDomain();

    close();

}

 

Please note that I’m using a few features available in modern C++ like smart-pointers and the new std::function (which is the equivalent of Action<T> in .NET). Once our application has loaded the CLR we can see it in the “Loaded Modules” in Qt Creator (shown highlighted “mscorlib”)

 

image

 

In the C#/CLR side of things (mono) we can see how we get the void* from Qt as an IntPtr, we then pin it with GCHandle.Alloc (this is to prevent the GC to move it around) and we pass it across to service implementation too, therefore we can bubble up or fire notifications back to the native world (Qt application). Below the implementation of InitializeHost and ShowMessage method, please note how we need to add the null-operator to the string we’re returning through the callback otherwise our strings in the Qt application might have some “funny” characters at the end.

/// <summary>

/// Initializes the host.

/// </summary>

/// <param name="callback">Callback.</param>

public void InitializeHost (IntPtr callback) {

    if (callback != IntPtr.Zero) {

        ExternalCallback = GCHandle.Alloc (Marshal.GetDelegateForFunctionPointer<Utilities.QtExternalCode> (callback));

        DaemonOperation.QtMessenger = new Action<string> (ShowMessage);

    }

 

    ShowMessage ("Creating binding...");

    var binding = new BasicHttpBinding ();

    binding.Security.Mode = BasicHttpSecurityMode.None; 

 

    ShowMessage ("Allocating endpoint addresses...");

    var address = new Uri ("http://localhost:8888/monodaemon");

    var addressMex = new Uri ("http://localhost:8888/monodaemon/mex");

 

    ShowMessage ("Creating host...");

    m_host = new System.ServiceModel.ServiceHost (typeof(DaemonOperation),

        new Uri[] { new Uri ("http://localhost:8888/monodaemon") }); 

 

    ShowMessage ("Adding behaviors...");

    m_host.Description.Behaviors.Remove<System.ServiceModel.Description.ServiceMetadataBehavior> ();

    m_host.Description.Behaviors.Add (new System.ServiceModel.Description.ServiceMetadataBehavior {

        HttpGetEnabled = true, HttpsGetEnabled = false

    }); 

 

    ShowMessage ("Adding endpoints...");

    m_host.AddServiceEndpoint (typeof(IDaemonOperation), binding, address);

    m_host.AddServiceEndpoint (System.ServiceModel.Description.ServiceMetadataBehavior.MexContractName, 

        System.ServiceModel.Description.MetadataExchangeBindings.CreateMexHttpBinding (), addressMex); 

 

    ShowMessage ("Starting host...");

    m_host.Open ();

}

 

/// <summary>

/// Shows the message.

/// </summary>

/// <param name="message">Message.</param>

protected void ShowMessage (string message)  {

    if (!string.IsNullOrEmpty (message)) {

        Console.WriteLine (message);

        var bytes = Encoding.Default.GetBytes (message);

        var buffer = new byte[bytes.Length + 1];

        Array.Copy (bytes, buffer, bytes.Length);

        buffer [buffer.Length - 1] = (byte) '\0';

        ((Utilities.QtExternalCode)ExternalCallback.Target).Invoke (buffer);

    }

}

 

 

 

As you can see, our native callback will be the interface between managed and native world. The ShowMessage method will output the notifications to the “Application Output” window in Qt Creator (System console)

 

image

 

but also to the ListBox (QListWidget) in the main window

 

image

 

The client application or the one driving the operations is a Windows Form application (depicted below)

image

One of the methods in our MonoDaemon (that’s the Qt application acting as WCF host, effectively) is StartRemoteProcess that can easily call a bash script or in this case we’re starting “gnome-calculator”. The image below shows my Ubuntu desktop along with “gnome-system-monitor” (highlighting qtMonoHost and mono is also visible there) and the application we started remotely (gnome-calculator)

 

image

 

The requests/responses are HTTP/SOAP so unlike my situation a few years back I had to do it with sockets. The WireShark image below shows the SOAP envelope returned to the client after having called the “GetRunningProcesses” method.

 

image

 

And that’s pretty much it, folks. Source code can be found here.

 

I would like to thank and dedicate this article to the people I love the most… God and his son Jesus, my wife and two young girls (I call them “3M”). Thanks for being patient and bearing with me when I sit in front of the PC for hours or go to bed late at night.

 

Regards,

 

Angel

Leave a Reply

Your email address will not be published. Required fields are marked *