.. _ex-monitoring:

Monitoring
==========

The goal is to create a C++ console application which connects to multiple turrets and prints the recieved notifications.

Like the :ref:`ex-loginlogout` example, we can also start this project from the ``connect`` example.

Creating the new project
------------------------

The steps for creating the new project are the same as :ref:`the logging in/out <ex-llnewproject>` naming this one for example ``monitoring``.


Making the monitoring server
----------------------------

To create a server we will need a ``apache::thrift::TProcessor``. Thrift generates one from the interface definition file (``TurretNotificationServiceProcessor``), but to create it we will need a realization of the :thrift:service:`TurretApi.TurretNotificationService` interface.
There is a generated ``TurretNotificationServiceNull`` class which is exactly that: a realization of the interface which does nothing, and that is suitable for now.

.. code-block:: cpp
   :emphasize-lines: 2,8,10

    #include <TurretRequestService.h>
    #include <TurretNotificationService.h>

    namespace turretapi = osxpert::turret::api;
    namespace osxthrift = osxpert::thrift;

    int main(int argc, char *argv[])
    {
      std::string serverAddress = "192.168.9.10";

      auto service = std::make_shared<turretapi::TurretNotificationServiceNull>();
      auto processor = std::make_shared<turretapi::TurretNotificationServiceProcessor>(service);

      osxthrift::ServerOptions serverOptions;
      serverOptions.ListenAddress = "0.0.0.0";
      serverOptions.ListenPort = 10052;

      osxthrift::Server server(processor, serverOptions);
      server.listen();

Now the server is up and running but there is no one connecting to it.

Connecting to Turrets and requesting notifications
--------------------------------------------------

Since we started from the ``connect`` example, a simple connection is already there. We just need to generalize it for optionally multiple turrets and add the notification request.

Collecting a few turret IP addresses into a vector

.. code-block:: cpp

    std::vector<std::string> ips{"192.168.8.69", "192.168.8.70", "192.168.8.71"};

And then iterating over these, connecting to each

.. code-block:: cpp
   :emphasize-lines: 3

    for (std::string ip : ips)
    {
      osxthrift::ClientOptions options{ip, 9007};

      std::shared_ptr<turretapi::TurretRequestServiceClient> client =
          osxthrift::makeClient<turretapi::TurretRequestServiceClient>(options);
      try
      {
        osxthrift::ClientConnection<turretapi::TurretRequestServiceClient> connection =
            osxthrift::connect(client);

        std::cout << "Connection successful to "
                  << options.Host << ":" << options.Port << std::endl;
      }
      catch (const std::exception &ex)
      {
        std::cout << ex.what() << std::endl;
      }
    }

Now when the temporary ``connection`` objects go out of scope, they get deleted and the destructor closes the connection. We could get around this by saving them into container so they do not get destructed, but for this example this is fine. We just have to request the notifications before disconnecting.

.. code-block:: cpp
   :emphasize-lines: 9-12,14

    try
    {
      osxthrift::ClientConnection<turretapi::TurretRequestServiceClient> connection =
          osxthrift::connect(client);

      std::cout << "Connection successful to "
                << options.Host << ":" << options.Port << std::endl;

      turretapi::SubscriberInfo subscriberInfo;
      subscriberInfo.ipAddress = serverAddress;
      subscriberInfo.port = serverOptions.ListenPort;
      subscriberInfo.turretName = ip;

      connection->requestNotifications(subscriberInfo);
    }


Adding the monitoring to the server
-----------------------------------

Now the server is up and running with turrets connected to it. What is left is to handle the notifications which are being sent.
For that we will need to define the methods in :thrift:service:`TurretApi.TurretNotificationService`.

.. code-block:: cpp

  ...
  namespace turretapi = osxpert::turret::api;
  ...

  class NotificationService : public turretapi::TurretNotificationServiceIf
  {
  public:
    void onLineStatesChanged(const std::string &turretName,
                             const turretapi::LineStatesData &states) override {}
    void onCallDataChanged(const std::string &turretName,
                           const turretapi::LineName &lineName,
                           const std::string &partyNumber,
                           const std::string &partyName,
                           const std::string &contactName) override {}
    void onLoginStateChanged(const std::string &turretName,
                             const turretapi::TurretState::type state) override {}
    void onLoginResponse(const std::string &turretName,
                         const turretapi::TurretErrorCode errorCode,
                         const std::string &errorDescription) override {}
    void onTopOfCallQueueChanged(const std::string &turretName,
                                 const turretapi::LineName &lineName) override {}
    void onRingTransferStateChanged(const std::string &turretName,
                                    const turretapi::RingTransferId &id,
                                    const turretapi::RingTransferState::type state) override {}
    void onRingTransferStateChangeError(const std::string &turretName,
                                        const turretapi::RingTransferId &id,
                                        const turretapi::TurretErrorCode errorCode,
                                        const std::string &errorDescription) override {}
    void onRingTransferSequenceStateChanged(const std::string &turretName,
                                            const turretapi::RingTransferId &id,
                                            const turretapi::RingTransferSequenceState::type state) override {}
    void onRingTransferSequenceStateChangeError(const std::string &turretName,
                                                const turretapi::RingTransferId &id,
                                                const turretapi::TurretErrorCode errorCode,
                                                const std::string &errorDescription) override {}
    void onInterfaceActionStateChanged(const std::string &turretName,
                                       const std::string &apiName,
                                       const turretapi::InterfaceActionState &state) override {}
    void onInterfaceActionKeyChanged(const std::string &turretName,
                                     const std::string &apiName,
                                     const turretapi::InterfaceActionKeyState::type keyState) override {}
  };
  ...

This might seem like a lot, but some IDEs have a function like "implement virtual methods" which can generate something like this.

This class is functionally the same as ``TurretNotificationServiceNull`` but now we can expand it - after we tell the service processor to use ours of course.

.. code-block:: cpp
   :emphasize-lines: 5

    int main(int argc, char *argv[])
    {
      std::string serverAddress = "192.168.9.10";

      auto service = std::make_shared<NotificationService>();
      auto processor = std::make_shared<turretapi::TurretNotificationServiceProcessor>(service);


Since we only want to print to the console, the body of the functions are fairly simple. For example:

.. code-block:: cpp

      void onLineStatesChanged(const std::string &turretName, const osxpert::turret::api::LineStatesData &states) override
      {
        std::cout << turretName << ": new line states for line: " << states.lineName << " | states: ";
        for (const auto &state : states.basicLineStates)
          std::cout << state << ", ";
        std::cout << std::endl;
      }

      void onCallDataChanged(const std::string &turretName, const osxpert::turret::api::LineName &lineName, const std::string &partyNumber, const std::string &partyName, const std::string &contactName) override
      {
        std::cout << turretName << ": conversing with " << partyNumber;
        if (!partyName.empty())
          std::cout << " " << partyName;
        if (!contactName.empty())
          std::cout << " " << contactName;
        std::cout << " on " << lineName << std::endl;
      }

      void onLoginStateChanged(const std::string &turretName, const osxpert::turret::api::TurretState::type state) override
      {
        std::cout << turretName << ": new login state: " << state << std::endl;
      }
      ...

Trying out our application
--------------------------

When we try out our monitoring application what we notice is that we only recieve notifications from the first turret we could connect to. This is because the underlying ``apache::thrift::server::ThreadPoolServer`` is set up to only spawn one worker thread, so we can only accept one connection. Now we can solve this by modifying the ``osxpert::thrift::Server`` to accept multiple or we can create the server ourselves.

.. code-block:: cpp
   :caption: The culprit in ThriftServer.cpp
   :emphasize-lines: 3

    Server::Server(std::shared_ptr<apache::thrift::TProcessor> processor, const ServerOptions& options)
     : ThreadFactory(std::make_shared<apache::thrift::concurrency::StdThreadFactory>()),
       ThreadManager(apache::thrift::concurrency::ThreadManager::newSimpleThreadManager(1))
    {
      ThreadManager->threadFactory(ThreadFactory);
      ...

Fixing the server
-----------------

The `Thrift C++ tutorial <https://thrift.apache.org/tutorial/cpp>`_ has an example for the usage of the different server types.
The `TThreadedServer <https://gitbox.apache.org/repos/asf?p=thrift.git;a=blob;f=tutorial/cpp/CppServer.cpp;hb=HEAD#l141>`_ seems to be good enough for our application. Bearing in mind that we need to use :ref:`TFramedTransport and TBinaryProtocol <connection>`.

.. code-block:: cpp

    #define NOMINMAX
    #include <thrift/server/TThreadedServer.h>
    #include <thrift/transport/TServerSocket.h>
    #include <thrift/transport/TBufferTransports.h>
    #include <thrift/protocol/TBinaryProtocol.h>


    serverOptions.ListenAddress = "0.0.0.0";
    serverOptions.ListenPort = 10052;

    apache::thrift::server::TThreadedServer server{
        processor,
        std::make_shared<apache::thrift::transport::TServerSocket>(serverOptions.ListenPort),
        std::make_shared<apache::thrift::transport::TFramedTransportFactory>(),
        std::make_shared<apache::thrift::protocol::TBinaryProtocolFactory>()};

.. note::

    The ``#define NOMINMAX`` line is there because on my environment winapi has a macro for ``min`` and ``max`` and it causes a compilation error for the similarly named members of ``std::numeric_limits`` which some thrift header uses.

Starting the server
-------------------

An other thing we lose is the automatic thread creation for starting the server because calling ``TThreadedServer::serve`` blocks execution until the server is stopped. And if we start it after we connect to the turrets they will not be able to connect to us.

Creating a separate thread for the server we can try out the application for real this time.

.. code-block:: cpp
   :emphasize-lines: 11-22,30-32

    ...
    int main(int argc, char *argv[])
    {
      ...
      apache::thrift::server::TThreadedServer server{
          processor,
          std::make_shared<apache::thrift::transport::TServerSocket>(serverOptions.ListenPort),
          std::make_shared<apache::thrift::transport::TFramedTransportFactory>(),
          std::make_shared<apache::thrift::protocol::TBinaryProtocolFactory>()};

      std::cout << "Starting the server thread..." << std::endl;
      std::thread serverThread {
        [&server] {
          try
          {
            server.serve();
          }
          catch (const std::exception &ex)
          {
            std::cout << ex.what() << std::endl;
          }
        }};

      std::vector<std::string> ips{"192.168.8.69", "192.168.8.70", "192.168.8.71"};

      ...

      std::cin.ignore();

      std::cout << "Stopping server..." << std::endl;
      server.stop();
      serverThread.join();
      return 0;
    }

.. code-block:: none
   :caption: Example output

    Starting the server thread...
    Connection successful to 192.168.8.69:9007
    Thrift: Fri Jan 17 19:07:42 2020 TSocket::open() connect() <Host: 192.168.8.70 Port: 9007>errno = 10061
    connect() failed: errno = 10061
    Connection successful to 192.168.8.71:9007
    192.168.8.69: new login state: Login
    192.168.8.69: new line states for line: 5643 | states: Preseized,
    192.168.8.69: conversing with 1239 SIPeti on 5643
    192.168.8.69: conversing with 1239 SIPeti on 5643
    192.168.8.69: new line states for line: 5643 | states: Conversation,
    192.168.8.69: conversing with 3619001239 Whitaker Harvey on 5643
    192.168.8.69: conversing with 3619001239 Whitaker Harvey on 5643
    192.168.8.69: new line states for line: 5643 | states: Connected, Conversation,
    192.168.8.71: new login state: Login
    192.168.8.71: new line states for line: 5643 | states: Connected, ConversationByAnother,
    192.168.8.71: conversing with 3619001239 Whitaker Harvey Peti SIP on 5643
    192.168.8.71: conversing with 3619001239 Whitaker Harvey Peti SIP on 5643
    192.168.8.69: new line states for line: 5644 | states: PreseizedByAnother,
    192.168.8.71: new line states for line: 5644 | states: Preseized,
    192.168.8.69: new line states for line: 5644 | states: Idle,
    192.168.8.71: new line states for line: 5644 | states: Idle,
    192.168.8.69: new line states for line: 5644 | states: PreseizedByAnother,
    192.168.8.71: new line states for line: 5644 | states: Preseized,
    192.168.8.69: conversing with 1259 on 5644
    192.168.8.69: conversing with 1259 on 5644
    192.168.8.69: new line states for line: 5644 | states: ConversationByAnother,
    192.168.8.71: conversing with 1259 on 5644
    192.168.8.71: conversing with 1259 on 5644
    192.168.8.71: new line states for line: 5644 | states: Conversation,
    192.168.8.69: conversing with 3619001259 Lamm Harold on 5644
    192.168.8.69: conversing with 3619001259 Lamm Harold on 5644
    192.168.8.71: conversing with 3619001259 Lamm Harold on 5644
    192.168.8.71: conversing with 3619001259 Lamm Harold on 5644
    192.168.8.69: new line states for line: 5644 | states: Connected, ConversationByAnother,
    192.168.8.71: new line states for line: 5644 | states: Connected, Conversation,
    192.168.8.69: new line states for line: 5643 | states: Idle,
    192.168.8.71: new line states for line: 5643 | states: Idle,
    192.168.8.69: new login state: Logout
    192.168.8.69: new line states for line: 3619001280 | states: Inactive,
    192.168.8.69: new line states for line: 3619005642 | states: Inactive,
    192.168.8.69: new line states for line: 5643 | states: Inactive,
    192.168.8.69: new line states for line: 5644 | states: Inactive,
    192.168.8.71: new line states for line: 5644 | states: Connected,
    192.168.8.71: new line states for line: 5644 | states: Idle,
    192.168.8.71: new login state: Logout
    192.168.8.71: new line states for line: 3619001280 | states: Inactive,
    192.168.8.71: new line states for line: 3619001285 | states: Inactive,
    192.168.8.71: new line states for line: 3619001288 | states: Inactive,
    192.168.8.71: new line states for line: 3619001289 | states: Inactive,
    192.168.8.71: new line states for line: 3619001286 | states: Inactive,
    192.168.8.71: new line states for line: 36190012873619001287 | states: Inactive,
    192.168.8.71: new line states for line: 3619005642 | states: Inactive,
    192.168.8.71: new line states for line: 5643 | states: Inactive,
    192.168.8.71: new line states for line: 5644 | states: Inactive,
    192.168.8.71: new line states for line: 5645 | states: Inactive,

    Stopping server...