ITエンジニアの技術メモ

神奈川在住のITエンジニアの備忘録です。おもにプログラミングやネットワーク技術について、学んだことを自分の中で整理するためにゆるゆると書いています。ちゃんと検証できていない部分もあるのでご参考程度となりますが、誰かのお役に立てれば幸いです。

Javaで時刻間の差分を出す

Javaで引数で受け取った2つの時刻間の差分を出すプログラム(calcTimeDiff)を作成した。ログ解析などをしていて、ログ内のある時刻からある時刻までの正確な経過時間が欲しい時、脳内でいちいち計算するのが面倒だったので、これを作成した。

使い方は、
java calcTimeDiff "開始時刻" "終了時刻"
で、"開始時刻" と "終了時刻" との差分を出す。

例えば、
java calcTimeDiff "2019/03/24 22:00:00" "2020/03/24 22:20:30"
として実行すると、結果は、
Diff Time: 366 days 0 hours 20 minutes 30 seconds
となる。

なお、対応している引数の時刻の形式は以下。
yyyy/MM/dd HH:mm:ss (例:2019/03/24 22:00:00)
yyyy/MM/dd HH:mm (例:2019/03/24 22:00)
yyyy/MM/dd HH (例:2019/03/24 22)
yyyy/MM/dd (例:2019/03/24)

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

public class calcTimeDiff {
    public static void main(String[] args) {
        if (args.length != 2) {
            throw new IllegalArgumentException("Both start date and end date are necessary.");
        }
        String startDateStr = args[0];
        String endDateStr   = args[1];
        long timeDiff = getEpochTime(endDateStr) - getEpochTime(startDateStr);

        if (timeDiff < 0) {
            throw new IllegalArgumentException("End date should be later than start date.");
        }
        printTimeDiff(timeDiff);
    }

    private static void printTimeDiff (long timeDiff) {
        int ONE_MIN_MILL_SEC_TIME   = 1000 * 60;
        int ONE_HOUR_MILL_SEC_TIME  = 1000 * 60 * 60;
        long ONE_DAY_MILL_SEC_TIME  = 1000 * 60 * 60 * 24;

        long days = timeDiff / (ONE_DAY_MILL_SEC_TIME);
        int restExceptDays = (int)(timeDiff % (ONE_DAY_MILL_SEC_TIME));
        int hours = restExceptDays / ONE_HOUR_MILL_SEC_TIME;
        int restExceptHours = restExceptDays % (ONE_HOUR_MILL_SEC_TIME);
        int mins = restExceptHours / ONE_MIN_MILL_SEC_TIME;
        int restExceptMins = restExceptHours % (ONE_MIN_MILL_SEC_TIME);
        int seconds = restExceptMins / 1000;

        System.out.printf("Diff Time: %d days %d hours %d minutes %d seconds", days, hours, mins, seconds);
    }

    private static long getEpochTime (String dateStr) {
        List<SimpleDateFormat> formatters = new ArrayList<SimpleDateFormat>();
        formatters.add(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"));
        formatters.add(new SimpleDateFormat("yyyy/MM/dd HH:mm"));
        formatters.add(new SimpleDateFormat("yyyy/MM/dd HH"));
        formatters.add(new SimpleDateFormat("yyyy/MM/dd"));

        long epochTime = 0;
        int counter = 0;
        for (SimpleDateFormat formatter : formatters) {
            try {
                epochTime = formatter.parse(dateStr).getTime();
                break;
            }
            catch (ParseException ex) {
                counter++;
                if (counter >= formatters.size()) {
                    throw new IllegalArgumentException("Failed to format the input date", ex);
                }
            }
        }
        return epochTime;
    }
}

SNMPv1トラップからSNMPv2トラップへの変換

SNMPv1トラップをSNMPv2トラップに変換する方法については、RFC2576に記載がある。

https://www.ietf.org/rfc/rfc2576.txt

の「3.1. Translating SNMPv1 Notification Parameters to SNMPv2 Notification Parameters」

上記の中でも snmpTrapOID の扱いはちょっと分かりにくいので、ここで簡単に纏めておく。まずは、該当箇所を抜粋。

(2) If the SNMPv1 generic-trap parameter is 'enterpriseSpecific(6)',
the SNMPv2 snmpTrapOID parameter SHALL be the concatentation of
the SNMPv1 enterprise parameter and two additional sub-
identifiers, '0', and the SNMPv1 specific-trap parameter.

(3) If the SNMPv1 generic-trap parameter is not '
enterpriseSpecific(6)', the SNMPv2 snmpTrapOID parameter SHALL
be the corresponding trap as defined in section 2 of RFC1907
[12]:

generic-trap parameter           snmpTrapOID.0
====================== =============
  0                                           1.3.6.1.6.3.1.1.5.1 (coldStart)
  1                                           1.3.6.1.6.3.1.1.5.2 (warmStart)
  2                                           1.3.6.1.6.3.1.1.5.3 (linkDown)
  3                                           1.3.6.1.6.3.1.1.5.4 (linkUp)
  4                                           1.3.6.1.6.3.1.1.5.5 (authenticationFailure)
  5                                           1.3.6.1.6.3.1.1.5.6 (egpNeighborLoss) 

 

上記によると、SNMPv1トラップのgeneric-trapがenterpriseSpecific(6)だったら、snmpTrapOIDは、<enterprise parameter>.0.<specific-trap parameter> となり、generic-trapがenterpriseSpecific(6)以外だったら、snmpTrapOIDは上記の表のようになる。

つまり、企業固有トラップだったら、snmpTrapOIDは「エンタープライズID.0.トラップ固有番号」となり、標準トラップだったら、上記の表のようになる。

Javaのプロセスに対して外部からGCを実行させる。

Javaのプロセスに対して外部からGC(ガベージ・コレクション)を実行させる方法についてちょっと調べた。幾つか方法があるようだが、自分でやってみてうまくいったのは以下の方法。

  • jconsole コマンドを実行して立ち上がるGUI上で、対象のJavaプロセスを選択し接続。表示画面の「メモリ」タブから「GCの実行」を押下。
  • jmap -histo:live <Javaプロセスのpid> を実行。

 

jconsole コマンドも jmap コマンドも、どちらもJavaが標準で持っているコマンドなので、簡単に実行できる。(Java 1.8にはどちらも入っていた。)

 

注意点としては、どちらのコマンドも、対象のJavaプロセスのユーザーと同じユーザーで実行する必要があるということ。「root ユーザだったら、それ以外のユーザーで実行されているJavaプロセスに対してもGCできるだろう」と思って、root ユーザで他のユーザで実行されているJavaプロセスに対して上述の方法でGCを試みたが、Javaプロセスに接続できなかった。

シェル上でコマンドを繰り返し実行する。

Linux のシェル上でコマンドを繰り返し実行したい時は、

  while true; do <実施したいコマンド>; sleep <実行間隔>; done

と実行する。これにより、<実施したいコマンド> が <実行間隔> で繰り返し実行される。終了させるには、このシェル上で ctrl + c を実行する。

 

例えば、vmstat -s コマンドを10秒間隔で繰り返し実行するには、

  while true; do vmstat -s; sleep 10s; done

とする。

 

こういうのを知っておくと、何かを繰り返し実行したい時、スクリプトなどを書かなくて済むので効率が上がる。

ネットワーク機器のループバックアドレス

ループバックアドレス」というと、自分自身を指す 127.0.0.1 (localhost) を思い浮かべる人が多いと思う。

ループバックアドレス(127.0.0.1)とは - IT用語辞典 e-Words

 

しかし、ルータ等のネットワーク機器が持つ「ループバックアドレス」はこれとは異なり、ネットワーク機器を管理するために使用される仮想インタフェースに割り当てられるアドレスのことを指すらしい。

 

以下を参考にさせて頂きました。

ループバック - Wikipedia

Cisco機器のインタフェース | ネットワークのおべんきょしませんか?

ループバックインターフェイスを設定する

LDAP の bindDN, BaseDN について

LDAPサーバを扱う時、分からない用語がちょくちょく出てくるので、、、ちょっとずつ纏めていきたいと思う。まずは、bindDN と baseDN について。

 

  • bindDN:LDAPサーバにログインする時に使用するユーザ。つまり、LDAPサーバへの問い合わせ自体に必要となるユーザのこと。
  • baseDN:LDAPサーバの持つオブジェクト・ツリー上で、どの配下からユーザ検索を行うのかを示す。

 

 以下を参考にさせて頂きましたm(_ _)m

https://yoshinorin.net/2018/10/31/ldap-auth-from-other-services/

https://milestone-of-se.nesuke.com/l7protocol/ldap/binddn-basedn/

httpリクエストのクエリストリングの値だけをURLエンコードする。

Javaで、httpリクエストのクエリストリングの値だけをURLエンコードするメソッド (encodeQueryValue) を作成した。

例えば、
「www.test.local/path/app?name1=aaa&name2=ほげ&name3=ふが」
を引数として与えると、
「www.test.local/path/app?name1=aaa&name2=%E3%81%BB%E3%81%92&name3=%E3%81%B5%E3%81%8C」
を返す。

以下は、作成した encodeQueryValue メソッドを使ったサンプル。

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class EncodeUrlSample {
    public static void main(String[] args) {
        String url = "www.test.local/path/app?name1=aaa&name2=ほげ&name3=ふが";
        String encodedURL = encodeQueryValue(url);
        System.out.println(encodedURL);
    }

    private static String encodeQueryValue (String url) {
        String[] urlParts = url.split("\\?");
        if (urlParts.length != 2) {
            // Nothing to do, so just return the original url.
            return url;
        }
        else {
            String hostAndPath = urlParts[0];
            String queryString = urlParts[1];
            String[] queryParams = queryString.split("&");
            StringBuilder sb = new StringBuilder();
            for (String queryParam : queryParams) {
                String[] nameAndValue = queryParam.split("=");
                // A pair of name and value.
                if (nameAndValue.length == 2) {
                    String name = nameAndValue[0];
                    String value = nameAndValue[1];
                    String encodedValue = null;
                    try {
                        encodedValue = URLEncoder.encode(value, "UTF-8");
                    }
                    catch (UnsupportedEncodingException ex) {
                        System.err.println("Can't encode URL: " + url);
                        ex.printStackTrace();
                        // Failed to encode, so just return the original url.
                        return url;
                    }
                    sb.append("&" + name + "=" + encodedValue);
                }
                // Unexpected format, so just add it.
                else {
                    sb.append("&" + queryParam);
                }
            }
            // "host and context path" + "?" + "query string".
            return (hostAndPath + "?" + sb.toString().substring(1));
        }
    }
}


「こんな面倒なことをしないで、url 全体に対して URLEncoder.encode() をかけてしまえば良いのでは?」という意見があると思うが、これをやると、
「www.test.local/path/app?name1=aaa&name2=ほげ&name3=ふが」
は、
「www.test.local%2Fpath%2Fapp%3Fname1%3Daaa%26name2%3D%E3%81%BB%E3%81%92%26name3%3D%E3%81%B5%E3%81%8C」
と変換され、パスやクエリの区切り文字などはエンコードして欲しくない場合でも強制的にエンコードされるので、上記のメソッドを作成した。