benjamin @Wiki 第3節 使用java語言來開發NAGIOS股價監視插件


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

在體驗完這個簡單的插件之後,接下來要登場的就是難度比較高一點的插件了。插件可以被任何語言所寫成,只要它是能被執行,並且反回特定格式的訊息,它的內容是c也好,java也好,perl也好,都是被允許的。用什麼語言來實作並不是一件重要的事,只要能夠達成我們想要的目的,就算及格,所以對java不熟的讀者,看到這一章也不用灰心,因為只要你懂了觀念,你可以用你所會的語言,來將這一個插件,進行改寫。


●需求定義與設計

所有的插件的產生,都是來自於需求。

假設,有一天,在上班的時後,專案經理對小明提出了一個要求,希望小明能夠利用nagios完美的性能,開發出一個定期監視台積電的股價的插件,並且在股價發生變動時,發一封信通知專案經理進場護盤。

接到這一個要求的小明馬上就列出了下面這一個規格書,並且請經理作確認:

輸入:股票代碼目標價 停損價

輸出:當股票現值超過目標價,則輸出警告,低於停損價,則發出危急

雖然經理只對台積電的股價有興趣,但是身為工程師的小明,總是想到軟體工程的一大守則,原件的重用性,所以,小明決定不要把程式碼寫死,留下空間讓專案經理來自行設定。

在這個簡單的介面定義書裡,我們也提供了高值與低值的設定選項,因為我們不曉得專案經理對於股價的企圖心在那裡,所以我們把這個設定值空出來讓經裡自行設定。

與專案經理進行確認之後,就開始要準備進行開發的工作。


●開發

小明評估了需求,以及時間,決定爭取時效,使用有很多方便的程式庫的java語言,來作為開發的工具。


第一步:取得即時的股價:

為了要知道股票目前最近的股價,唯一的來源就是一些入口網站提供的服務了。所以小明決定到yahoo財經,來取得即時的股價。比方說,台積電的股價,只要訪問下列這一個網站,就可以得到所需的資料了。

http://tw.stock.yahoo.com/q/q?s=2330

為了要能夠及時的去把這一個網頁的資料抓回來,小明決定使用由JakartaCommons所提供的HttpClient程式庫。這一個專案的網頁位在

http://jakarta.apache.org/commons/httpclient/

在下載回來之後,小明使用了這個程式庫,寫了一段抓取網頁的程式碼:


程式碼:

String url="http://tw.stock.yahoo.com/q/q?s=2330";

  //生成一個httpclient的物件
  HttpClient client=new HttpClient();

  //生成一個get方法的物件
  GetMethod method = new GetMethod(url);
  intstatusCode = client.executeMethod(method);
  if(statusCode != HttpStatus.SC_OK) {
   //如果訪問失敗時的處理;
    System.out.println("失敗:"+method.getStatusLine());
  }
//網頁連線成功,開始取得網頁的資料  

  InputStream responseBody=method.getResponseBodyAsStream();
  //使用中文的編碼,準備將網頁讀進來
  BufferedReader br=new BufferedReader(new InputStreamReader(responseBody, "Big5") );



網頁讀取進來之後,一大段的文字並沒有什麼實用的價值,小明必須要想辦法在這一大段的文字裡,找到一個專案經裡所關心的資訊,也就是當時的股價。此時考慮到時效性,小明決定不採用由另外一個專案所提供的html解析套件,改採暴力破解法。

小明使用了文字編輯器,將取得的html原始碼一行一行的往下數下來時,發現當前的股價所記載的位置,正好是在第164行的位置。於是,小明寫了一個回圈,來將取得的大段文章裡,找到164行的內容。正可謂是,股海淘淘,只取一飄呀!

程式碼:

 BufferedReaderbr=newBufferedReader(newInputStreamReader(responseBody, "Big5") );
  String line=null;
  intline_no=0;
  while((line = br.readLine())!= null ){
   line_no++;
   if(line_no ==164){
  


取回來的這一飄,長的像這個樣子:
<tdalign="center"bgcolor="#FFFfff"nowrap><b>54.00</b></td>
在重重包圍之下的那個54.00,就是我們最終想要得到的結果了。
此時只好在對這一行文字進行第二次的加工,將不需要的部分去除,只留下我們想要的。
程式碼 3
#
進行到這裡,小明已經將取得股價的原件給制作完成了,把他整合成一個method,程式碼如下方所示:
程式碼 4:用來取得即時股價的方法
#
小明程式寫到這裡,覺得松了一口氣,因為核心的部份已經完成了一大部份了。他決定到樓下的starbuck買杯咖啡,順便上個廁所,在回來寫下面的一段。

第二部:比較股價,產生輸出
回到位子上之後,小明開始進行第二階段的開發,也就是將剛剛所得到的即時價格,來與設定的高值,低值,作一個比較,如果高值大於現在值,則反回警告,如果現在值小於低值,則發出危險訊號,通知專案經理作停損。
代碼如下所示:
程式碼:價何比較的方法
privatestatic String compareValues(intcode , floatcurrent, floattarget_price , float low_price ) {
  intstatus =-1;
  String result="UNKNOWN";
  String message="["+code +"]current:"+current+",targetis"+target_price+",lowis"+low_price;
  if(current > target_price ) status =0;
  elseif(current > low_price ) status =1;
  elseif(current < low_price ) status =2;
  
  switch(status) {
  case0:
   result="WARNING;";
   break;
  case1:
   result="OK;";
   break;
  case2:
   result="CRITICAL;";
   break;
  case-1:
   result="UNKNOWN;";
   break;

  }
  returnresult+message;
 }

在以上的方法裡我們作了二件事情,一件事是價格比較,另一件事將結果以nagios所規定的格式與以反回,並且報知目前的價格,以便讓使用者可以在cgi的畫面上得到更多的資訊。

?

?


三、選項的讀取

在前面的二個步驟之後,接下來所剩下的,就是讀取外部的選項了。在之前的介面定義書裡,我們知道這個程式需要接受三個選項,而且我們也決定要遵守開發的原則,在使用者輸入了錯誤的選項之時,能夠回送出適當的提示訊息,所以我們決定,使用由由JakartaCommons所提供的CLI:Command LineInterface程式庫。這一個專案的網頁位在

http://jakarta.apache.org/commons/cli/

在參照了文件的說明之後,小明依照需求,寫出了如下的程式碼:

?

?

程式碼:
 Options options =new Options();
  //option 1
  Option target_price_opt = new Option("tp", true,
    "TargetPrice,forexample -tp=58.5 ");
  target_price_opt.setRequired(true );
  //  option2
  options.addOption(target_price_opt);
  Option low_price_opt = new Option("lp", true,
    "LowPrice,forexample -lp=58.5 ");
  low_price_opt.setRequired(true );
  //  option3
  options.addOption(low_price_opt);
  Option code_opt = new Option("c",true,"code,forexample-c=2330");
  code_opt.setRequired(true );
  options.addOption(code_opt);

  //3.然後parseinput

  CommandLineParser parser = new BasicParser();

  CommandLine cmd;

  try{

   cmd = parser.parse(options, args);

  }

  catch(ParseException pe) {

   //4.發生錯誤就用usage() method給使用這提示

   usage(options);

上面我們預先設定了code,top price,lowprice的三個選項,當使用者有輸入三個值的時後,才讓程式運行,沒有輸入或是輸入不正確,少了那些項目之時,則列印出錯誤的訊息,來提醒使用者。這一個程式庫有一個好處是,我們定義好選項之後,其他的事情我們都不用多寫程式碼,比方說我們要印出輔助訊息的method,程式碼只有下面這幾行:


程式碼:
 private static voidusage(Optionsoptions){
  HelpFormatter formatter = new HelpFormatter();

  formatter.printHelp("CommandLineUI", options);

 }

是不是相當的簡單呢?

接下來,我們要把在使用者所輸入的這三個值,一一的傳給之前所開發的程式,傳遞的方法,分別是:

將code 傳給 網頁取得method

將tp,lp傳給價格比較method

程式碼如下所示:

程式碼:
  if(cmd.hasOption("tp")){
   target_price=Float.parseFloat(cmd.getOptionValue("tp"));
  }
  if(cmd.hasOption("lp")){
   low_price=Float.parseFloat(cmd.getOptionValue("lp"));
  }
  if(cmd.hasOption("c")){
   code=Integer.parseInt(cmd.getOptionValue("c"));
  }
  //System.out.println("code = "+code);
  
  try{
  floatcurrent= getCurrentValue(code);
  System.out.println(compareValues(code,current,target_price,low_price));
  } catch(Exception e) {
   System.out.println("Critical;"+e.getMessage() );
  }

在這個式裡我們將比較的結果利用system.out來作輸出,這結果會直接被傳送到stdout上,然後由nagios所接受。

到此,小明已經完成了核心程式的工作,在此我們先將完整的程式碼給列出來:

股價監視程式完整程式列表:
#

接下來我們要進行編譯的動作,由於這個程式我們用到了

1 httpclient

2 cli

這兩個程式庫,所以我們在編譯時,必須要將這兩個含式庫加到路徑裡面:

javac -cp XXXX.jar:XXXXX.jar :./ App.java

完成之後,我們試著執行所產生的class檔,注意,因為我們程式碼裡有引用這兩個程式庫,所以執行之時,也一樣要在路徑裡作宣告。

java -cp XXXX.jar:XXXXX.jar :./ App

此時,因為我們什麼都沒有輸入,所以依照所排演的,應該要吐出一些訊息:




還有也作出了下列的元件部屬圖

#元件部屬圖
# 元件部屬圖 #
# # #
# 由以上的圖可以看出,在黑盒子的部份是主要開發的原件,這個原件,而由於在server上要運行java的程式,需要設定一些路徑,所以為了簡單起見,小明寫了一個shell語言,來將執行時的複雜部份包裝起來。 #

我們將可以進行設定的部份以java語言來實作,將會是如以下的部份:

因為需要接受語多的命令,所以採用了common之下的getopt這一個方便精巧的功具:

程式碼如下所示:

這個工具可以幫助我們產生一些命令行,提示使用者該如何使用命令,以及讀取使用者的命令,非常的方便。

還有,我們也知道股價的變動是一天不會超過或低於百分之七的,所以如果經裡的設定值,與現在的股價比起來,如果低於這個範圍,那我們就可以使得這一個插件休兵一天,不用一直去查詢網站。而相反的,如果說這一天的股價,很有可能到達這一個設定值,則我們就要希望能夠增加我們訪問這個股價的數量。

將這一個邏輯以java語言來作表示的話,將會是如下的範例:

接下來是這個程式核心的邏輯部份,也就是我們要從網站上得到股價:

這個時候我們使用的是commons程式庫的httpclient

我們始我下面這一段程式碼來存取網站:

程式碼將如下所示:

這個程式碼送出一個get要求,從yahoo網站上得到了台積電網頁。

這一個網頁,裡的第一行是股價,所以我們試著取得網頁上的第一行。

程式碼如下所示:

然後這一個股價的位置位於這一行的第三個TDTAG之間,所以我們利用一個簡單的regexp,來取得這一個股價的位置。

到目前為止,我們已經完成了這一個插件的大半部份,接下來就是將它們組合起來,成為一個完整的程式:

將這個程式放到nagiosserver上,接著,進行編譯及測式:

javacxxxx

執行這個程式:

java–cpxxxxxxxxx

結果我們發現這一個結果:

依照這一個命令所是示的,我們假設我們的股價是100元,作出以下的設定:

結果nagios反回了這一個結果:

因為現在台積電的股價還低於設定值,所以ok

接著我們假設我們的目標股價是50

出現了下列這一個訊息:

critical

果然台積電的股價以經有點危險了。

看來沒什麼問題了。接下來我們將這個plugin安裝到nagios上。

然後,如果這個股價超過了這一個臨界值之後,通當會超過之後就一直超過,或是超過之後又低回來,當股價超過之後,如果一直不停的發送信息也不是辦法。

所以我們要對這個細節,來對nagios作出設定:

設定檔如下所示:

一切就緒之後,接下來我們打開畫面,出現了以下的場景:

接下來的問題,如果要將這個plug擴充成不只讓台責電能夠使用,該怎麼作好?

我想很多程式高手們早就發覺該怎麼做了,不過我還是雞婆一下把這個程式碼給出來,看了這個程式完成之後,我們就可以將我們所想要注意的股票一隻一隻的登入上去:

設定檔如下所示:

畫面如下所示:

不過,這一個程式還有一個問題,那就是程式股價如果到了設定值了,就會一直呈現緊急的狀態,當然,此時我們可以從新的設定我們的目標,或者是經由cgi來關閉股價的通知。

或者,你也可以提供一個到達目標之後,自動再設定目標的超強大機能,不過這一個內容,就交由讀者來自行發揮了。




packagecom.kbmj.someproject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

importorg.apache.commons.cli.BasicParser;
importorg.apache.commons.cli.CommandLine;
importorg.apache.commons.cli.CommandLineParser;
importorg.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
importorg.apache.commons.cli.ParseException;
importorg.apache.commons.httpclient.HttpClient;
importorg.apache.commons.httpclient.HttpException;
importorg.apache.commons.httpclient.HttpStatus;
importorg.apache.commons.httpclient.methods.GetMethod;

public class App {
 publicstatic voidmain(String args[]){
  floattarget_price = 0;
  floatlow_price = 0;
  intcode=2330;

  Options options = new Options();
  //option 1
  Option target_price_opt = new Option("tp", true,
    "TargetPrice,forexample -tp=58.5 ");
  target_price_opt.setRequired(true );
  //  option2
  options.addOption(target_price_opt);
  Option low_price_opt = new Option("lp", true,
    "LowPrice,forexample -lp=58.5 ");
  low_price_opt.setRequired(true );
  //  option3
  options.addOption(low_price_opt);
  Option code_opt = new Option("c",true,"code,forexample-c=2330");
  code_opt.setRequired(true );
  options.addOption(code_opt);

  //3.然後parseinput

  CommandLineParser parser = new BasicParser();

  CommandLine cmd;

  try{

   cmd = parser.parse(options, args);

  }

  catch(ParseException pe) {

   //4.發生錯誤就用usage() method給使用這提示

   usage(options);

   return;

  }
  //
  if(cmd.hasOption("tp")){
   target_price=Float.parseFloat(cmd.getOptionValue("tp"));
  }
  if(cmd.hasOption("lp")){
   low_price=Float.parseFloat(cmd.getOptionValue("lp"));
  }
  if(cmd.hasOption("c")){
   code=Integer.parseInt(cmd.getOptionValue("c"));
  }
  //System.out.println("code = "+code);
  
  try{
  floatcurrent= getCurrentValue(code);
  System.out.println(compareValues(code,current,target_price,low_price));
  } catch(Exception e) {
   System.out.println("Critical;"+e.getMessage() );
  }
  
 }

 privatestatic String compareValues(intcode , floatcurrent, floattarget_price , float low_price ) {
  intstatus =-1;
  String result="UNKNOWN";
  String message="["+code +"]current:"+current+",targetis"+target_price+",lowis"+low_price;
  if(current > target_price ) status =0;
  elseif(current > low_price ) status =1;
  elseif(current < low_price ) status =2;
  
  switch(status) {
  case0:
   result="WARNING;";
   break;
  case1:
   result="OK;";
   break;
  case2:
   result="CRITICAL;";
   break;
  case-1:
   result="UNKNOWN;";
   break;

  }
  returnresult+message;
 }

 privatestatic voidusage(Optionsoptions) {
  HelpFormatter formatter = new HelpFormatter();

  formatter.printHelp("CommandLineUI", options);

 }
 privatestatic floatgetCurrentValue(int code) throwsHttpException, IOException{
  floatp=-1;
  String url="http://tw.stock.yahoo.com/q/q?s="+code;

  //HttpClientの生成
  HttpClient client=new HttpClient();

  //methodインスタンスの生成
  GetMethod method = new GetMethod(url);
  intstatusCode = client.executeMethod(method);
  if(statusCode != HttpStatus.SC_OK) {
   //notok;
   return p;
   //System.out.println("失敗:"+method.getStatusLine());
  }
//  HTMLソースを取得
  InputStream responseBody=method.getResponseBodyAsStream();
  //getdatainnoXXX
  BufferedReader br=new BufferedReader(new InputStreamReader(responseBody, "Big5") );
  String line=null;
  intline_no=0;
  while((line = br.readLine())!= null ){
   line_no++;
   if(line_no ==164){
  
   
  }
  returnp;
 }
}