Friday, April 28, 2017

Resource control - rc-03

This program (rc-03) uses the Solaris interface to resource control.
It lists all the controls that are active to its running process.
Following the listing there's a sample output.

#include <rctl.h>
#include <alloca.h>

#include <cstdlib>
#include <cerrno>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <iostream>
#include <iomanip>

//
// Convenience "special" vector.

//
template< typename T >
struct vector : public std::vector< T >
{
};
 
//
// Convenience operator for just the "special" vector. 
// For simplicity, not dealing with formatting or state issues.
//
template< typename T >
std::istream & operator >> ( std::istream & is, vector<T> & v )
{
  typename vector<T>::value_type element;

  if ( is >> element )
    v.push_back( element );

  return is;
}

//
// The main data structure for organization.
// Key: resource containment.
// Data: list of controls.
//
std::map< const std::string, vector< std::string > > resource;

//

// The resource walking routine.
// 
int active( const char * name, void * )
{
  std::istringstream iss( name );
  std::string containment;

  if ( std::getline( iss, containment, '.' ) )
    iss >> resource[ containment ];

  return 0;
}

const size_t size = ::rctlblk_size( );
typedef ::rctlblk_t * p_blk;

// May suffer from stream formatting issues...
void print_limit( const p_blk rl )
{
  //
  // Type
  //

  std::cout
    << "\t\t"
    << std::setw( 11 ) << std::left;

  switch ( ::rctlblk_get_privilege( rl ) )
  {
      case RCPRIV_BASIC:
          std::cout << "basic";
          break;

      case RCPRIV_PRIVILEGED:
          std::cout << "privileged";
          break;

      case RCPRIV_SYSTEM:
          std::cout << "system";
          break;
  }
 
  //
  // Value
  //

  std::cout
     << ":"
     << std::setw( 21 ) << std::right
     << ::rctlblk_get_value( rl );

  std::cout
     << std::endl; 
 
  std::cout 
     << std::setw( 0 ) << std::left;
}

// May suffer from stream formatting issues... 
void print_control( std::string containment, 
                    vector< std::string > & control_list )
{ 
  p_blk rl = static_cast < p_blk >
             (
                 // Never leaks memory
                 ::alloca( size )
             );

  containment += ".";
 
  std::sort( control_list.begin( ), control_list.end( ) );

  for ( auto & control : control_list )
  {
    std::cout
       << "\t";
  
    std::cout
     << std::setw( 32 ) << std::left
     << control;


    const std::string name = containment + control;
   
    p_blk p = 0; // Tracking pointer
    int rv = ::getrctl( name.c_str(), p, rl, RCTL_FIRST );

    const int gflags = ::rctlblk_get_global_flags( rl );
    std::cout
     << std::setw( 9 ) << std::right
     <<
     (
     gflags & RCTL_GLOBAL_BYTES   ? "(bytes)" :
     gflags & RCTL_GLOBAL_SECONDS ? "(seconds)" :
     gflags & RCTL_GLOBAL_COUNT   ? "(count)" :
     "(?)"
     )
     << std::endl;

    std::cout
     << std::setw( 0 ) << std::left;


    while ( rv == 0 )
    {
        print_limit( rl );

        p = rl; // Smart(?) move...
        rv = ::getrctl( name.c_str(), p, rl, RCTL_NEXT );
    }

    if ( errno != ENOENT )
      std::cout
         << "\t\tError getting limits!"
         << std::endl;
   
    std::cout
         << std::endl;
  }
}

// May suffer from stream formatting issues... 
void print( )
{
  std::cout
     << std::endl
     << "Printing active resource controls..."
     << std::endl
     << std::endl;

  // I want to list containments as ordered below
  auto known = { "process", "task", "project", "zone" };

  for ( auto & containment : known )
  {
    std::cout
       << containment
       << std::endl
       << std::endl;
   
    print_control( containment, resource[ containment ] );
  }

  //
  // In case new (currently unknown) containments appear
  // in the future, just list them at the bottom.
  //
  for ( auto & r : resource )
  {
    auto c = r.first; // A containment
    if ( std::none_of( known.begin( ), known.end( ),
            [ &c ]( const char * s ) { return s == c; } ) )
    {
      std::cout
         << c // An unknown containment
         << std::endl
         << std::endl;
     
      print_control( r.first, r.second );
    }
  }
}

// May suffer from stream formatting issues... 
int main( )
{
  std::cout
     << std::endl
     << "Gathering active resource controls..."
     << std::endl
     << std::endl;

  std::cout
     << "\tRC walk starting..."
     << std::endl;

  if ( ::rctl_walk( active, 0 ) == 0 )
    std::cout
     << "\tRC walk completed successfuly!"
     << std::endl;

  print( );

  return EXIT_SUCCESS;
}


A possible output is:

$ ./rc-03

Gathering active resource controls...

    RC walk starting...
    RC walk completed successfuly!

Printing active resource controls...


process

    max-address-space             (bytes)
        privileged : 18446744073709551615
        system     : 18446744073709551615

    max-core-size                 (bytes)
        privileged :  9223372036854775807
        system     :  9223372036854775807

    max-cpu-time                (seconds)
        privileged : 18446744073709551615
        system     : 18446744073709551615

    max-data-size                 (bytes)
        privileged : 18446744073709551615
        system     : 18446744073709551615

    max-deferred-posts            (count)
        basic      :                   32
        privileged :                  100
        system     :                 8192

    max-file-descriptor           (count)
        basic      :                 1024
        privileged :                65536
        system     :           2147483647

    max-file-size                 (bytes)
        privileged :  9223372036854775807
        system     :  9223372036854775807

    max-itimers                   (count)
        privileged :                 1000
        system     :                65536

    max-msg-messages              (count)
        privileged :                 8192
        system     :           4294967295

    max-msg-qbytes                (bytes)
        privileged :                65536
        system     : 18446744073709551615

    max-port-events               (count)
        privileged :                65536
        system     :           2147483647

    max-sem-nsems                 (count)
        privileged :                  512
        system     :                32767

    max-sem-ops                   (count)
        privileged :                  512
        system     :           2147483647

    max-sigqueue-size             (count)
        basic      :                  128
        privileged :                  512
        system     :                 8192

    max-stack-size                (bytes)
        basic      :              8388608
        privileged :        1098437885952
        system     :        1098437885952

task

    max-cpu-time                (seconds)
        system     : 18446744073709551615

    max-lwps                      (count)
        system     :           2147483647

    max-processes                 (count)
        system     :           2147483647

project

    cpu-cap                       (count)
        system     :           4294967295

    cpu-shares                    (count)
        privileged :                    1
        system     :                65535

    max-contracts                 (count)
        privileged :                10000
        system     :           2147483647

    max-crypto-memory             (bytes)
        privileged :           2143089664
        system     : 18446744073709551615

    max-locked-memory             (bytes)
        system     : 18446744073709551615

    max-lwps                      (count)
        system     :           2147483647

    max-mrp-ids                   (count)
        privileged :                  128
        system     :             16777216

    max-msg-ids                   (count)
        privileged :                  128
        system     :             16777216

    max-port-ids                  (count)
        privileged :                 8192
        system     :                65536

    max-processes                 (count)
        system     :           2147483647

    max-sem-ids                   (count)
        privileged :                  128
        system     :             16777216

    max-shm-ids                   (count)
        privileged :                  128
        system     :             16777216

    max-shm-memory                (bytes)
        privileged :           2143089664
        system     : 18446744073709551615

    max-tasks                     (count)
        system     :           2147483647

zone

    cpu-cap                       (count)
        system     :           4294967295

    cpu-shares                    (count)
        privileged :                    1
        system     :                65535

    max-locked-memory             (bytes)
        system     : 18446744073709551615

    max-lofi                      (count)
        system     : 18446744073709551615

    max-lwps                      (count)
        system     :           2147483647

    max-mrp-ids                   (count)
        system     :             16777216

    max-msg-ids                   (count)
        system     :             16777216

    max-processes                 (count)
        system     :           2147483647

    max-sem-ids                   (count)
        system     :             16777216

    max-shm-ids                   (count)
        system     :             16777216

    max-shm-memory                (bytes)
        system     : 18446744073709551615

    max-swap                      (bytes)
        system     : 18446744073709551615



Thursday, April 27, 2017

Resource control - rc-02

This program (rc-02) uses the Solaris interface to resource control.
The main() function was listed first just to let the logic more clear.
Note how its approach is more complex than the traditional one:

#include <rctl.h> 
#include <strings.h>
#include <alloca.h>
#include <unistd.h>

  
#include <cerrno>
#include <cstdlib>
#include <iostream>

  
const ::pid_t ppid = ::getppid( );
const size_t size = ::rctlblk_size(); 
static const char name[] = "process.max-file-descriptor";

typedef ::rctlblk_t * p_blk;

// May suffer from stream formatting issues... 
int main( )
{
    std::cout
        << std::endl
        << "Resource limit: " << name
        << std::endl;

    
    p_blk p = 0; // Tracking pointer
    p_blk rl = static_cast < p_blk > 

               (
                   // Never leaks memory
                   ::alloca( size ) 
               );

    int rv = ::getrctl( name, p, rl, RCTL_FIRST );
    while ( rv == 0 )
    {

        print( rl );

        p = rl; // Smart(?) move...
        rv = ::getrctl( name, p, rl, RCTL_NEXT );
    }


    if ( errno != ENOENT )
    {

        ...
        return EXIT_FAILURE;
    }


    return EXIT_SUCCESS;
}


And now the auxiliary printing function:

// May suffer from stream formatting issues... 
void print( const p_blk rl )
{

    std::cout << std::endl;

    //

    // Type
    //

    std::cout << "\tType: ";
    switch (
::rctlblk_get_privilege( rl ) )
    {
        case RCPRIV_BASIC:
            std::cout << "Basic";
            break;

        case RCPRIV_PRIVILEGED:
            std::cout << "Privileged";
            break;

        case RCPRIV_SYSTEM:
            std::cout << "System";
            break;
    }

  
    //
    // PID & PPID
    //

    const ::id_t pid = ::rctlblk_get_recipient_pid( rl );

    std::cout << " (PID " << pid;

    if ( pid > -1 )
        std::cout << "; PPID " << ppid;

    std::cout << ")" << std::endl;


 
   //
    // Flags
    //

    const int gflags = ::rctlblk_get_global_flags( rl );

    std::cout << "\tGlobal action: "
        <<
        (
        gflags == RCTL_GLOBAL_NOACTION ? "no action" :
        gflags & RCTL_GLOBAL_DENY_ALWAYS ? "deny" :
        gflags & RCTL_GLOBAL_DENY_NEVER ? "allow" :
        "other"
        )
        << "; ";

    std::cout << "Syslog: "
        <<
        (
        gflags & RCTL_GLOBAL_SYSLOG ? "yes" : "no"
        )
        << std::endl;

    std::cout << "\tLowerable: "
        <<
        (
        gflags & RCTL_GLOBAL_LOWERABLE ? "yes" : "no"
        )
        << "; ";

    std::cout << "Infinite: "
        <<
        (
        gflags & RCTL_GLOBAL_INFINITE ? "yes" : "no"
        )
        <<
std::endl;

    const int lflags = ::rctlblk_get_local_flags( rl );

    std::cout << "
\tLocally maximal: "
        <<
        (
        lflags & RCTL_LOCAL_MAXIMAL ? "yes" : "no"
        )
        <<
"; ";

    std::cout << "Local project: "
        <<
        (
        lflags & RCTL_LOCAL_PROJDB ? "yes" : "no"
        )
        << std::endl;
 

 
    std::cout << "\tLimit type: "
        <<
        (
        gflags & RCTL_GLOBAL_BYTES ? "bytes" :
        gflags & RCTL_GLOBAL_SECONDS ? "seconds" :
        gflags & RCTL_GLOBAL_COUNT ? "count" :
        "?"
        )
        << std::endl;

   
    //
    // Values
    //

    std::cout << "\tCurrent:  "
        << ::rctlblk_get_value( rl )
        << std::endl;

    std::cout << "\tEnforced: "
        << ::rctlblk_get_enforced_value( rl )
        << std::endl;

}
  
This program works in Solaris 11.3.
I used the GCC 4.8.2 and NetBeans 8.1 with no issues at all.

Resource control - rc-01

This program (rc-01) uses the UNIX portable interface to resource control.
This is the traditional (legacy) standard as well.

Note how simple it is!
But the recommended Solaris approach is far more complex...

#include <sys/resource.h>

#include
<cstdlib>
#include
<iostream> 
  
// May suffer from stream formatting issues... 
int main( )
{
    ::rlimit rl;

    if ( ::getrlimit( RLIMIT_NOFILE, &rl ) != 0 )
    {
        ...
        return EXIT_FAILURE;
    }

    std::cout << "Process' inherited FD limits:" << std::endl;
    std::cout << "Current: " << rl.rlim_cur << "." << std::endl;
    std::cout << "Maximum: " << rl.rlim_max << "." << std::endl;

    return EXIT_SUCCESS;
}


This program works in Solaris 11.3.
I used the GCC 4.8.2 and NetBeans 8.1 with no issues at all.
 

Monday, April 24, 2017

Resource control - Intro

Resource control historically appeared for limiting the system's resources that processes and their children could consume, but nowadays in Solaris this concept has been elaborated to other collections of processes: tasks, projects and zones as well.

The best practice is to carefully assess (as using extended accounting) the resource consumption of the workloads on the system before applying any fine-grained resource control to prevent over-consumptions. And, of course, above all, the system must meet or exceed the combined resource requirements of all the workloads it's supposed to host.

This topic is vast because there are many resources (resource-controls(5)) ranging from the most "elementary" to the most complex ones, there 3 control levels (basic, privileged and system), there are 4 containment levels (process, task, project and zone), 2 types of actions and flags (local and global) as well more than one available interface managing part (ulimit(1) and getrlimit(2)) or all of this stuff (rctladm(1M), prctl(1), setrctl(2), the projects database and zone configuration).

All the manpages provide extensive information I won't discuss, at least for now. In addition there are some other lenghty references such as the chapter 5 of Resource Management and Oracle® Solaris Zones Developer's Guide which is a kind of revamp of the original chapter 5 of the (partially) archived Solaris Containers: Resource Management and Solaris Zones Developer's Guide.
 

Saturday, April 22, 2017

Sessions & Process Groups

In the very start of the endeavor to the daemons' underground world it's inevitable to be acquainted with a few, perhaps new, essential concepts about processes in terms of how they are collected within the system.

Without further digressions, processes are collected into process groups. But there's an elaboration on top of this leading to another concept called sessions, which is associated to the historical concept of job control from a time lacking the pletora of GUI terminals if not, perhaps, just the rare and crude xterm which some ancient seems to inconditionally love.

Although not really a big mistery, the descriptions about these concepts (process groups, sessions and their related) seems unecessarily convoluted in Intro(2) and termio(7I), which can partially obfuscate understanding its central apparatus. My intention is, thus, to address these obstacules by attempting to streamline some ideas and clarify some of these concepts definitions and descriptions:

PROCESS GROUP
A collection of processes that begins when created by a given process which is thereof defined as its process group leader. The process group ends when the last process in the process group ends of leaves the process group.

During its lifetime it's uniquely identified by a positive integer called the process group ID (PGID), which exactly matches the process ID (PID) of its process group leader and furnishes a way of signaling all its member processes as a whole.

Any process that is not a process group leader may create a new process group or a new session (becoming itself the process group leader and, eventually, the session leader of the newly created entity) or yet may join another process group in the same session.

Each process in the system is a member of a single process group and every newly created process joins the process group and session of its parent (process).
SESSION
A collection of process groups that begins when created by a given process which is thereof defined as its session leader. The session ends when the last process in the session ends of leaves the session. 

During its lifetime it's uniquely identified by a positive integer called the session ID (SID), which exactly matches the process ID (PID) of its original session leader. The original session leader is also the process group leader for the session's originally first process group, hence its process ID (PID) matches its session ID (SID) and its process group ID (PGID) and, as such, the session ID (SID) also matches its originally first process group ID (PGID).

A session is capable of being biunivocally (one-to-one) associated to a terminal (the controlling terminal, /dev/tty according to tty(7D)) by the session leader (the controlling process) which establishes the connection. As sessions are inherited by new processes created within it, so are the controlling terminals, although this association can be broken if these new processes establish a new session (setsid(2)). The controlling terminal can send/handle certain (typically quit and interrupt) signals to the process groups in the (controlling process') session that, in general, will cause them to stop, unless they have made arrangements to mask this out or are orphaned process groups.

Orphaned process groups are those that share the controlling terminal but whose processes' parents do not belong to any other process group in the same session.

Among the process groups in the session, the controlling terminal will distinguish one of them as the foreground process group, thereby exclusively granting it certain terminal access prerrogatives, and treat all the other process groups in that (controlling process') session as background process groups. By default, the controlling process' (session leader's) process group is assigned as the foreground process group.
 
As for a real world example of a few of these concepts in action applied to a daemon creation you may be interested in browsing the output from truss applied to a sample daemon creation code around the fork() and setsid() system calls and associated PIDs and PPIDs numbers.