空色ブログ

anything about Solaris


« cat /dev/urandom | メイン | Sakila Sample Databa... »
水曜日 7 09, 2008

libkstat の使い方

はじめに

  • 先日のエントリで vmstat や mpstat は kstat という仕組みを使って統計情報を収集していると書きました。今回はこの kstat を自分のプログラムから使う方法を簡単にご紹介したいと思います。kstat には広範囲の統計情報が用意されており、これを使用する事で mpstat 相当のプログラムを書けるほか、自分の関心のある統計情報を採集するプログラムを自由に書く事が可能です。

参考資料

ソースコード

kstat(3KSTAT) – kernel statistics facility

Solaris Kernel Statistics - Accessing libkstat with C

kstat について

概要

  • kstat はカーネル内の統計情報を収集するフレームワークです。デバイスドライバ等のカーネルモジュールは、報告したい統計情報があると kstat に統計情報のエントリを作成し必要に応じてそれを更新します。kstat に上がってくるデータの種類は多岐に渡りますが、mpstat や vmstat, iostat, netstat 等の Solaris の統計情報コマンドで得られる情報の多くは kstat から取られています。
  • kstat の情報はカーネル内に蓄積されます。この情報をアプリケーションから読み出す為に libkstat と呼ばれるライブラリが用意されています。vmstat 等のコマンドも、この libkstat で用意されている関数を使用して kstat の情報にアクセスしています。libkstat は C 言語用のインターフェイスですが、libkstat から派生した Perl のモジュールも提供されています。
    • ldd でライブラリのリンクを調べると、 vmstat コマンドは libkstat にリンクされている事が分かります
    •  # ldd /bin/vmstat 
               libdevinfo.so.1 =>       /lib/libdevinfo.so.1
               libkstat.so.1 =>         /lib/libkstat.so.1
               libc.so.1 =>     /lib/libc.so.1
               libnvpair.so.1 =>        /lib/libnvpair.so.1
               libsec.so.1 =>   /lib/libsec.so.1
               libgen.so.1 =>   /lib/libgen.so.1
               libnsl.so.1 =>   /lib/libnsl.so.1
               libavl.so.1 =>   /lib/libavl.so.1
               libmp.so.2 =>    /lib/libmp.so.2
               libmd.so.1 =>    /lib/libmd.so.1
               libscf.so.1 =>   /lib/libscf.so.1
               libdoor.so.1 =>  /lib/libdoor.so.1
               libuutil.so.1 =>         /lib/libuutil.so.1
               libm.so.2 =>     /lib/libm.so.2
      
    • kstat コマンドは Perl 用の kstat インターフェイスを使用しています
    •  # head -20 /bin/kstat
       #!/usr/perl5/bin/perl
       #
       #
       # Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
       # Use is subject to license terms.
       #
       #ident  "@(#)kstat.pl   1.4     04/11/16 SMI"
       #
       
       require 5.6.1;
       use strict;
       use warnings;
       use locale;
       use Getopt::Std;
       use POSIX qw(locale_h ctime);
       use File::Basename;
       use Sun::Solaris::Utils qw(textdomain gettext gmatch);
       use Sun::Solaris::Kstat;
       
       #
      

kstat コマンド

  • 特定の kstat のデータを取り出したり、全データをダンプしたい場合には kstat コマンドを使用すると便利です。上で書いた通り kstat コマンドは Perl で記述されています。
  • -p オプションを付けると 1 エントリ 1 行で表示されます
  •  # kstat -p | grep vminfo
     unix:0:vminfo:class     vm
     unix:0:vminfo:crtime    0
     unix:0:vminfo:freemem   9512127911
     unix:0:vminfo:snaptime  77433.828728951
     unix:0:vminfo:swap_alloc        4457779140
     unix:0:vminfo:swap_avail        48903066506
     unix:0:vminfo:swap_free 49891099563
     unix:0:vminfo:swap_resv 5445812197
    
  • kstat コマンドの詳細は "man -s 1M kstat" をご覧下さい

データ構造

kstat の階層構造 -- 'module:instance:name:parameter'

  • kstat の統計情報はモジュール名、インスタンス番号、名前、パラメータ名で管理されています。モジュール名は誰が情報を提供しているか、インスタンス番号はモジュール内のインスタンスの番号、名前はパラメータが属するカテゴリ、パラメータ名は何の情報であるかを端的に表した名称です。
  • 'cpu:0:sys:syscall' という kstat エントリが合った場合、"cpu" モジュールのインスタンス 0 番、"sys" カテゴリの "syscall" パラメータである事を示しています。幾つか例外もありますが、モジュール名はカーネルモジュールの名前をそのまま使用している場合が殆どです。マルチプロセッサシステムの CPU 統計情報の様に、同じモジュールで複数の実態がある場合はインスタンス番号が振られます。
  • kstat のデータ構造の詳細については "man -s 3KSTAT kstat" も合わせてご参照下さい

kstat コントロール構造体

  • kstat はカテゴリ毎にエントリを持っており、全てのエントリはリンクリストで繋がっています。リンクリスト全体は kstat チェーンと呼ばれています。
  • kstat のエントリにアクセスするには、まず kstat コントロール構造体を取得する必要があります
    • kstat コントロール構造体は以下の様な構造体です
    •  typedef struct kstat_ctl {
               kid_t   kc_chain_id;    /* kstat チェーンの ID       */
               kstat_t *kc_chain;      /* kstat チェーンへのポインタ */
               int     kc_kd;          /* /dev/kstat 記述子 */
       } kstat_ctl_t;
      
    • kc_chain が kstat チェーンへのポインタです。このポインタを手繰ると kstat エントリのリンクリストを取得する事が出来ます。

kstat 構造体

  • kstat チェーンは以下の構造体のリストになっています
  •  typedef struct kstat {
             /*
              * カーネルとユーザランド両方に関するフィールド
              */
             hrtime_t        ks_crtime;      /* 作成時間 */
             struct kstat    *ks_next;       /* 次のエントリ */
             kid_t           ks_kid;         /* 個別の kstat ID */
             char            ks_module[KSTAT_STRLEN]; /* モジュール名 */
             uchar_t         ks_resv;        /* 予約 */
             int             ks_instance;    /* インスタンス番号 */
             char            ks_name[KSTAT_STRLEN]; /* 名前 */
             uchar_t         ks_type;        /* データタイプ */
             char            ks_class[KSTAT_STRLEN]; /* クラス名 */
             uchar_t         ks_flags;       /* フラグ */
             void            *ks_data;       /* データ領域へのポインタ */
             uint_t          ks_ndata;       /* データの個数 */
             size_t          ks_data_size;   /* データ領域の大きさ */
             hrtime_t        ks_snaptime;    /* 前回のスナップショットの時間 */
             /*
              * カーネルにのみ関係するフィールド
              */
             int             (*ks_update)(struct kstat *, int); /* dynamic update */
             void            *ks_private;    /* arbitrary provider-private data */
             int             (*ks_snapshot)(struct kstat *, void *, int);
             void            *ks_lock;       /* protects this kstat's data */
     } kstat_t;
    
  • kstat 構造体は kstat のカテゴリ毎に作成されます
  • この kstat 構造体にモジュール名、インスタンス番号、名前が記述されていますので、これを目印にして ks_next ポインタを順番に辿って行く事で目的のデータを見つける事が出来ます
  • ks_data ポインタが指す先が統計情報のデータです。ks_data ポインタの先に ks_ndata 個のデータが格納されています。
  • 統計情報のデータには幾つかの形式があり、どの形式であるかは ks_type で判別します。ks_type が KSTAT_TYPE_NAMED の場合、名前と値がペアになったデータ形式です。

kstat_named 構造体

  • KSTAT_TYPE_NAMED 形式のデータは kstat_named 構造体に格納されています。
  •  typedef struct kstat_named {
             char    name[KSTAT_STRLEN];     /* パラメータ名 */
             uchar_t data_type;              /* データ型 */
             union {
                     char            c[16];  /* enough for 128-bit ints */
                     int32_t         i32;
                     uint32_t        ui32;
                     struct {
                             union {
                                     char            *ptr;   /* 文字列 */
     #if defined(_KERNEL) && defined(_MULTI_DATAMODEL)
                                     caddr32_t       ptr32;
     #endif
                                     char            __pad[8]; /* 64-bit padding */
                             } addr;
                             uint32_t        len;    /* # bytes for strlen + '\0' */
                     } str;
     #if defined(_INT64_TYPE)
                     int64_t         i64;
                     uint64_t        ui64;
     #endif
                     long            l;
                     ulong_t         ul;
     
                     /* These structure members are obsolete */
     
                     longlong_t      ll;
                     u_longlong_t    ull;
                     float           f;
                     double          d;
             } value;                        /* カウンタの値 */
     } kstat_named_t;
    
  • 統計情報のデータは value 共用体の部分に入っていますので、データ型に合わせて取り出します

libkstat について

  • アプリケーションから kstat の統計情報にアクセスするには libkstat を使用します。

libkstat の使い方

  • /usr/lib/libkstat.so に 32bit 版のライブラリがあります。SPARC マシンでは /usr/lib/sparcv9/libkstat.so に 64bit 版のライブラリがあり、x86-64 マシンでは /usr/lib/amd64/libkstat.so に 64bit 版があります。libkstat を使用したプログラムをコンパイルする際には -lkstat をリンカのフラグに追加して下さい。
  • libkstat に関連するヘッダファイルは /usr/include/kstat.h と /usr/include/sys/kstat.h です。sys/kstat.h は kstat.h から呼び出されます。sys/kstat.h には kstat を利用する際に知っておくと便利な情報が記述されていますので、是非ご一読下さい。

libkstat の関数群

  • libkstat で提供されている関数の中で特に重要なのは kstat_open(3KSTAT), kstat_close(3KSTAT), kstat_chain_update(3KSTAT), kstat_lookup(3KSTAT), kstat_data_lookup(3KSTAT), kstat_read(3KSTAT) の 6 つです
  • kstat_open(3KSTAT)
    • kstat_open() は kstat コントロール構造体を取得するのに使用します
    • プロトタイプは kstat_ctl_t *kstat_open(void)
    • kstat_open() を呼び出すと kstat コントロール構造体と kstat チェーンが初期化されます
    •  kstat_ctl_t * kc = kstat_open();
      
  • kstat_close(3KSTAT)
    • kstat_close() は kstat コントロール構造体に紐付けられたリソースを開放します
    • プロトタイプは int kstat_close(kstat_ctl_t *kc)
    •  kstat_close(kc);
      
  • kstat_chain_update(3KSTAT)
    • kstat_chain_update() は kstat チェーンにエントリが追加/削除されていないかを確認し、変更が合った場合は kstat チェーンを更新します
    • プロトタイプは kid_t kstat_chain_update(kstat_ctl_t *kc)
    •  kstat_chain_update(kc);
      
  • kstat_lookup(3KSTAT)
    • kstat_lookup() はモジュール名、インスタンス番号、名前から kstat 構造体を検索します
    • プロトタイプは kstat_t *kstat_lookup(kstat_ctl_t *kc, char *ks_module, int ks_instance, char *ks_name)
    • "e1000g" モジュールに属する kstat 構造体を探してくる例
    •  kstat_t *ksp = kstat_lookup(kc, "e1000g", -1, NULL);
      
  • kstat_data_lookup(3KSTAT)
    • kstat_data_lookup() は ks_data が指すデータ領域から name で渡されたパラメータ名のデータを取得します
    • プロトタイプは void *kstat_data_lookup(kstat_t *ksp, char *name)
    • "ifspeed" というパラメータ名のデータを取得する例
    •  kstat_named_t *knp = kstat_data_lookup(ksp, "ifspeed");
       uint64_t data = knp->value.ui64;
      
  • kstat_read(3KSTAT)
    • kstat_read() は kstat 構造体の指すデータ領域をカーネルからコピーします
    • プロトタイプは kid_t kstat_read(kstat_ctl_t *kc, kstat_t *ksp, void *buf)
    •  kstat_read(kc, ksp, NULL);
      

libkstat を使用したプログラムの流れ

  • まずは kstat.h をインクルードします
  •  #include <kstat.h>
    
  • kstat コントロール構造体を取得します
  •  kstat_ctl_t *kc = kstat_open()
    
  • kstat 構造体を検索します
  •  kstat_t *ksp = kstat_lookup(kc, "module", instance_num, "name");
    
  • データ領域をコピーします
  •  kstat_read(kc, ksp, NULL);
    
  • データを取得します
  •  kstat_named_t *knp = kstat_data_lookup(ksp, "parameter");
     uint64_t data = knp->value.ui64;
    
  • kstat チェーンが更新されていないか確認します
  •  if(kstat_chain_update(kc)) ...
    
  • 後は kstat 構造体の検索から、必要なだけ繰り返します
  •  ...
    
  • 必要な処理が終了したらリソースを開放します
  •  kstat_close(kc);
    

e1000g と kstat

  • kstat の実例として e1000g を取り上げてみます
  • e1000g は Intel PRO/1000 ファミリのネットワークインターフェイス用のドライバです。VMWare や VirtualBox の上で Solaris を起動すると、NAT 用のインターフェイスとしてこの e1000g が使用される様です。
  • e1000g も kstat にデータが用意されており、ネットワークをモニタリングする際に非常に役に立ちます。どんな情報があるかは以下の kstat コマンドの出力をご覧下さい。
  • kstat のデータを絞り込んで出力するには -p オプションに続けて 'module:instance:name:parameter' を指定する方法がお薦めです
  •  # kstat -p 'e1000g:0:e1000g0:'
     e1000g:0:e1000g0:brdcstrcv     0
     e1000g:0:e1000g0:brdcstxmt     0
     e1000g:0:e1000g0:class net
     e1000g:0:e1000g0:collisions    0
     e1000g:0:e1000g0:crtime        25.173112519
     e1000g:0:e1000g0:ierrors       0                  // 受信エラー数
     e1000g:0:e1000g0:ifspeed       1000000000         // リンクスピード
     e1000g:0:e1000g0:ipackets      73148              // 受信パケット数
     e1000g:0:e1000g0:ipackets64    73148              // 受信パケット数 64bit 版
     e1000g:0:e1000g0:multircv      0
     e1000g:0:e1000g0:multixmt      0
     e1000g:0:e1000g0:norcvbuf      0
     e1000g:0:e1000g0:noxmtbuf      0
     e1000g:0:e1000g0:obytes        2211356            // 送信バイト数
     e1000g:0:e1000g0:obytes64      2211356            // 送信バイト数 64bit 版
     e1000g:0:e1000g0:oerrors       0                  // 送信エラー数
     e1000g:0:e1000g0:opackets      32507              // 送信パケット数
     e1000g:0:e1000g0:opackets64    32507              // 送信パケット数 64bit 版
     e1000g:0:e1000g0:rbytes        101348315          // 受信バイト数
     e1000g:0:e1000g0:rbytes64      101348315          // 受信バイト数 64bit 版
     e1000g:0:e1000g0:snaptime      18151.778089914
     e1000g:0:e1000g0:unknowns      9
    
  • e1000g 用の kstat のモジュール名は "e1000g" です。インスタンス番号は ifconfig や dladm で出力されるインターフェイス番号と同じです。パラメータは送受信パケット数 (ipackets, opackets)、送受信データサイズ (rbytes, obytes)、リンクアップスピード (ifspeed) 等、様々な物が用意されています。
  • もちろん e1000g だけでなく、nxge, ce, bge 等 Solaris に用意されているネットワークインターフェイスはどれも同じような情報を提供しています
  • また、これより更に詳細な 'e1000g::statistics:' というエントリも用意されています

サンプルコード -- e1000g0stat

  • libkstat を実際に使用して e1000g の統計情報を監視するツールを作成してみます

e1000g0stat

  • e1000g0stat は e1000g0 の送受信データ料、送受信パケット数、エラー数を出力するコマンドです
  •  # cat e1000g0stat.c
     #include <kstat.h>
     
     #define SLEEP_TIME 1
     
     int main() {
       kstat_ctl_t *kc;
       kstat_t *ksp;
       kstat_named_t *knp;
       uint64_t obytes, opackets, rbytes, ipackets;
       uint32_t oerrors, ierrors;
     
       kc = kstat_open();
       ksp = kstat_lookup(kc, "e1000g", 0, "e1000g0");
       kstat_read(kc, ksp, NULL);
     
       knp = kstat_data_lookup(ksp, "obytes64");
       obytes = knp->value.ui64;
     
       knp = kstat_data_lookup(ksp, "opackets64");
       opackets = knp->value.ui64;
     
       knp = kstat_data_lookup(ksp, "oerrors");
       oerrors = knp->value.ui64;
     
       knp = kstat_data_lookup(ksp, "rbytes64");
       rbytes = knp->value.ui64;
     
       knp = kstat_data_lookup(ksp, "ipackets64");
       ipackets = knp->value.ui64;
     
       knp = kstat_data_lookup(ksp, "ierrors");
       ierrors = knp->value.ui64;
     
       printf("  ----------- output -----------");
       printf("  ----------- input ------------\n");
       printf("        Mbps   packets    errors");
       printf("        Mbps   packets    errors\n");
     
       while(1) {
         sleep(SLEEP_TIME);
     
         if(kstat_chain_update(kc)) {
           ksp = kstat_lookup(kc, "e1000g", 0, "e1000g0");
         }
     
         kstat_read(kc, ksp, NULL);
     
         knp = kstat_data_lookup(ksp, "obytes64");
         printf("    %8d", (knp->value.ui64 - obytes) * 8 / 1024 / 1024);
         obytes = knp->value.ui64;
     
         knp = kstat_data_lookup(ksp, "opackets64");
         printf("  %8d", knp->value.ui64 - opackets);
         opackets = knp->value.ui64;
     
         knp = kstat_data_lookup(ksp, "oerrors");
         printf("  %8d", knp->value.ui32 - oerrors);
         oerrors = knp->value.ui32;
     
         knp = kstat_data_lookup(ksp, "rbytes64");
         printf("    %8d", (knp->value.ui64 - rbytes) * 8 / 1024 / 1024);
         rbytes = knp->value.ui64;
     
         knp = kstat_data_lookup(ksp, "ipackets64");
         printf("  %8d", knp->value.ui64 - ipackets);
         ipackets = knp->value.ui64;
     
         knp = kstat_data_lookup(ksp, "ierrors");
         printf("  %8d", knp->value.ui32 - ierrors);
         ierrors = knp->value.ui32;
     
         puts("");
     
       }
     
       kstat_close(kc);
     }
    
  • e1000g に複数のインスタンスがあった場合の処理、e1000g 以外のネットワークインターフェイスへの対応、より精度の高いインターバル、桁溢れへの対応等々、ちゃんとしたコマンドに仕立てる為にはもう少し手間が必要ですが、libkstat の使い方の説明に的を絞って色々と手を抜き、、、もとい省略していますのでご注意下さい

e1000g0stat の使い方

  • まずは ifconfig コマンドで e1000g0 のインターフェイスが存在する事を確認して下さい
  •  # ifconfig e1000g0
     e1000g0: flags=1004843<UP,BROADCAST,RUNNING,MULTICAST,DHCP,IPv4> mtu 1500 index 2
             inet 192.168.247.128 netmask ffffff00 broadcast 192.168.247.255
             ether 0:c:29:26:ed:fc 
    
  • 以下のコマンドでコンパイルして下さい
  •  # gcc -lkstat e1000g0stat.c -o e1000g0stat
    
  • ftp 等のトラフィックが発生している状態でコマンドを実行します
  •  # ./e1000g0stat
       ----------- output -----------  ----------- input ------------
             Mbps   packets    errors        Mbps   packets    errors
                2      4964         0         383     33153         0
                2      4927         0         380     32881         0
                2      4392         0         332     28702         0
                2      4538         0         315     27287         0
                2      4415         0         301     26036         0
     ^C
    
  • 機能的には "netstat -i" や "dladm -s" と近いですが、ネットワークトラフィックを Mbps 単位で出力出来るのは便利です
  • この程度であれば僅か数十行で書けてしまいますので、気軽に libkstat をお使い頂けると思います

さいごに

  • libkstat の使い方をごく簡単にですがご紹介させて頂きました。既存の統計情報取得コマンドのソースコードを読んだり、新しいコマンドを作られる際のお役に立てば幸いです。前回のエントリに引き続きマニアックな小ネタになってしまいました…

投稿されたコメント:

すばらしい情報をありがとうございます。本当に助かりました。
教えて頂いたこと忘れぬよう、関数ライブラリ化してずっと使い続けたいと思います。

Posted by 匿名希望 on 10月月 22日, 2008年 at 09:54 午後 JST #

cool

Posted by wow gold on 11月月 03日, 2008年 at 10:46 午前 JST #

コメント
  • HTML文法 不許可

Today's Page Hits: 57