--[[

  ●LAN 側回線使用率監視スクリプト
  一定の監視間隔毎に LAN 側回線使用率(送信/受信)を監視し、設定した閾値を超え
  たら管理者にメールを送信して知らせるスクリプトです。

  送信/受信の負荷が閾値を指定回数だけ連続して超えた場合にメールを送信します。
  その後、負荷が閾値を指定回数だけ連続して下回った場合には、正常値に戻ったと
  判断します。設定値 down_mail を true に設定している場合には、正常値に戻った
  際にもメールを送信します。

  <説明>
  ・このファイルを RTFS か外部メモリに保存してください。
  ・本項目の config の設定では schedule at コマンドでルーター起動時に Lua スク
   リプトが実行されるように設定しています。
  ・スクリプトを停止するときは terminate lua コマンドを実行してください。
  ・再度、Lua スクリプトを実行する場合は lua コマンドで実行してください。
  ・★マークの付いた設定値は変更が可能です。

  <ノート>
 ・メールの送信失敗時に出力する SYSLOG レベルを指定可能です。
  SYSLOG のレベルを指定するには、log_level を設定してください。
  debug レベル、notice レベルの SYSLOG を出力するためには、それぞれ以下の設定
  が必要です。
   debug レベル ・・・ syslog debug on
   notice レベル・・・ syslog notice on
 ・本スクリプトファイルを編集する場合、文字コードは必ず Shift-JIS を使用してく
  ださい。

]]


--------------------------##  設定値  ##--------------------------------
-- 監視間隔(1 - 864000 秒)
idle_time = (監視間隔)			-- ★

-- 回線使用率を計測する時間(1, 2 .. )
avr_sec = (計測時間)			-- ★

-- 監視する LAN インターフェースの番号
lan_num = (LAN インターフェースの番号)	-- ★

-- 送信負荷率の閾値(1 - 99 %)
snd_th = (負荷率)			-- ★

-- 受信負荷率の閾値(1 - 99 %)
rcv_th = (負荷率)			-- ★

-- 連続で閾値を超えたら異常と判断する回数、または正常な状態に復帰したと判断する回数(1, 2 ..)
count = (回数)				-- ★

-- 正常な状態に復帰した場合にもメールを送るか否か(送る: true / 送らない: false)
down_mail = (true / false)		-- ★

-- メールの設定
mail_tbl = {				-- ★
	smtp_address = "(SMTP サーバのアドレス)",
	from = "(送信元メールアドレス)",
	to = "(宛先メールアドレス)"
}

-- メールの送信に失敗したときに出力する SYSLOG のレベル (info, debug, notice)
log_level = "(SYSLOG レベル)"		-- ★

----------------------##  設定値ここまで  ##----------------------------


------------------------------------------------------------
-- LAN インターフェースの速度を求める関数                 --
------------------------------------------------------------
function interface_speed(num)
	local rtn, str, val, rt_name
	local cmd = "show config"
	local ptn = "speed lan" .. tostring(num) .. " (%d+%a)"

	rtn, str = rt.command(cmd)
	if (rtn) and (str) then
		str = str:match(ptn)
		if (str) then
			val = unitstr2num(str)
		end
	end

	if (not val) or (val == 0) then
		rt_name = string.match(_RT_FIRM_REVISION, "(%w+) ")

		if ((rt_name == "RTX5000") or (rt_name == "RTX3510") or (rt_name == "RTX3500") or (rt_name == "RTX1300") or (rt_name == "RTX1220") or (rt_name == "RTX1210") or (rt_name == "RTX1200") or (rt_name == "RTX830") or (rt_name == "RTX810") or (rt_name == "NVR700W") or (rt_name == "NVR510") or (rt_name == "NVR500")) then
			val = unitstr2num("1000m")
		elseif (rt_name == "SRT100") then
			val = unitstr2num("100m")
		end
	end

	return val
end

------------------------------------------------------------
-- 回線速度を数値に変換する関数                           --
------------------------------------------------------------
function unitstr2num(str)
	local val, unit

	val = tonumber(str:match("%d+"))
	unit = str:sub(-1):lower()
	
	if (unit == "k") then
		val = val * 1024
	elseif (unit == "m") then
		val = val * 1024 * 1024
	else
		val = 0
	end

	return val
end

------------------------------------------------------------
-- show status lan コマンドの実行結果                     --
-- から回線使用率を求める関数                             --
------------------------------------------------------------
function lan_load_info(num, sec)
	local rtn, str1, str2, rcv, snd
	local t = {}
	local cmd = "show status lan" .. tostring(num)
	local ptn = "%((%d+)%s+オクテット"

	rtn, str1 = rt.command(cmd)
	if (rtn) and (str1) then
		rt.sleep(sec)

		rtn, str2 = rt.command(cmd)
		if (rtn) and (str2) then
			str1 = str1 .. str2

			n = 1
			for w in string.gmatch(str1, ptn) do
				t[n] = w
				n = n + 1
			end

			if (t[1]) and (t[3]) then
				snd = ((tonumber(t[3]) - tonumber(t[1])) * 8) /sec
			else
				rtn = false
				str1 = "使用帯域(送信)の取得失敗\r\n"
			end
			if (t[2]) and (t[4]) then
				rcv = ((tonumber(t[4]) - tonumber(t[2])) * 8) /sec
			else
				rtn = false
				str1 = "使用帯域(受信)の取得失敗\r\n"
			end
		else
			str1 = str2
		end
	else
		str1 = cmd .. "コマンドの実行失敗\r\n"
	end

	return rtn, rcv, snd, str1
end

----------------------------------------------------------------
-- 回線使用率が閾値を超えたとき、または正常に復帰したときに   --
-- メッセージを返す関数                                       --
----------------------------------------------------------------
function make_lanmsg(tbl, val, th, down)
	local rtn
	local str = ""

	if (val) then
		rtn = count_proc(tbl, val, th)
		if (rtn < 0) then
			if (down) then
				str = tbl.title .. "負荷率が閾値以下の値に下がりました。\r\n"
			end
		elseif (rtn > 0) then
			str = tbl.title .. "負荷率が閾値を超えました。\r\n"
			str = str .. string.format("  %s負荷率: %d%%\r\n  閾値: %d%%\r\n\r\n",
			 		tbl.title, val, th)
		end
	end

	return str
end

------------------------------------------------------------
-- 閾値を超えた、または下回った連続回数をカウントする関数 --
------------------------------------------------------------
function count_proc(t, val, th)
	local rtn = 0

	if (val > th) then
		if (not t.flag) then
			t.over = t.over + 1
			if (t.over == count) then
				rtn = 1
				t.flag = true
			end
		else
			if (t.down > 0) then
				t.down = 0
			end
		end
	else
		if (t.flag) then
			t.down = t.down + 1
			if (t.down == count) then
				rtn = -1
				t.flag = false
				t.over = 0
				t.down = 0
			end
		else
			if (t.over > 0) then
				t.over = 0
			end
		end
	end

	return rtn
end

------------------------------------------------------------
-- 現在の日時を取得する関数                               --
------------------------------------------------------------
function time_stamp()
	local t

	t = os.date("*t")
	return string.format("%d/%02d/%02d %02d:%02d:%02d", 
		t.year, t.month, t.day, t.hour, t.min, t.sec)
end

------------------------------------------------------------
-- メインルーチン                                         --
------------------------------------------------------------
local rtn, str, max
local snd, rcv
local cnt_tbl = {
			rcv = {over = 0, down = 0, flag = false, title = "受信"},
			snd = {over = 0, down = 0, flag = false, title = "送信"}
}
max = interface_speed(lan_num)

while (true) do
	mail_tbl.text = ""

	rtn, rcv, snd, str = lan_load_info(lan_num, avr_sec)
	if (rtn) then
		snd = 100*snd/max
		rcv = 100*rcv/max
		mail_tbl.text = mail_tbl.text .. make_lanmsg(cnt_tbl.rcv, rcv, rcv_th, down_mail)
		mail_tbl.text = mail_tbl.text .. make_lanmsg(cnt_tbl.snd, snd, snd_th, down_mail)
	else
		mail_tbl.text = str
	end

	if (mail_tbl.text:len() > 0) then
		mail_tbl.subject = string.format("[LAN %d] transmit/receive load watch (%s)", lan_num, time_stamp())
		rtn = rt.mail(mail_tbl)
		if (not rtn) then
			rt.syslog(log_level, "failed to send mail. utilization.lua")
		end
	end

	rt.sleep(idle_time)
end