開始之初的架構設計決定著軟件產品的生死存亡。“好的開始相當于成功一半”。
開始的架構設計也是最難的,需要調研同類產品的情況以及技術特征,了解當前世界上對這種產品所能提供的理論支持和技術平臺支持。再結合自己項目的特點(需要透徹的系統分析),才能逐步形成自己項目的架構藍圖。
比如要開發網站引擎系統,就從Yahoo的個人主頁生成工具 到虛擬主機商提供的網站自動生成系統,以及IBM Webphere Portal的特點和局限 從而從架構設計角度定立自己產品的位置。
好的設計肯定需要經過反復修改,從簡單到復雜的循環測試是保證設計正確的一個好辦法
由于在開始選擇了正確的方向,后來項目的實現過程也驗證了這種選擇,但在一些架構設計的細部方面,還需要對方案進行修改,屬于那種螺旋上升的方式,顯然這是通過測試第一的思想和XP工程方法來實現的。
如果我們開始的架構設計在技術平臺定位具有一定的世界先進水平,那么,項目開發實際有一半相當于做實驗,是研發,存在相當的技術風險。
因此,一開始我們不可能將每個需求都實現,而是采取一種簡單完成架構流程的辦法,使用最簡單的需求將整個架構都簡單的完成一遍(加入人工干 預),以檢驗各個技術環節是否能協調配合工作(非常優秀先進的兩種技術有時無法在一起工作),同時也可以探知技術的深淺,掌握項目中的技術難易點。這個過 程完成后,我們就對設計方案做出上面的重大修改,豐富完善了設計方案。
設計模式是支撐架構的重要組件
架構設計也類似一種工作流,它是動態的,這點不象建筑設計那樣,一開始就能完全確定,架構設計伴隨著整個項目的進行過程之中,有兩種具體操作保證架構設計的正確完成,那就是設計模式(靜態)和工程項目方法(RUP或XP 動態的)。
設計模式是支撐架構的一種重要組件,這與建筑有很相象的地方,一個建筑物建立設計需要建筑架構設計,在具體施工中,有很多建筑方面的規則和模式。
我們從J2EE藍圖模式分類http://java.sun.com/blueprints/patterns/catalog.html中就可以很清楚的看到J2EE這樣一個框架軟件的架構與設計模式的關系。
架構設計是骨架,設計模式就是肉
這樣,一個比較豐富的設計方案可以交由程序員進一步完成了,載輔助以適當的工程方法,這樣就可保證項目的架構設計能正確快速的完成。
時刻牢記架構設計的目標
由于架構設計是在動態中完成的,因此在把握架構設計的目標上就很重要,因此在整個項目過程中,甚至每一步我們都必須牢記我們架構設計的總體目標,可以概括下面幾點:
1. 最大化的重用:這個重用包括組件重用 和設計模式使用等多個方面。
比如,我們項目中有用戶注冊和用戶權限系統驗證,這其實是個通用課題,每個項目只是有其內容和一些細微的差別,如果我們之前有這方面成功研發經 驗,可以直接重用,如果沒有,那么我們就要進行這個子項目的研發,在研發過程中,不能僅僅看到這個項目的需求,也要以架構的概念去完成這個可以稱為組件的 子項目。
2. 盡可能的簡單明了:我們解決問題的總方向是將復雜問題簡單化,其實這也是中間件或多層體系技術的根本目標。但是在具體實施設計過程中,我們可能會將簡單問題復雜化,特別是設計模式的運用上很容易范這個錯誤,因此如何盡可能的做到設計的簡單明了是不容易的。
我認為落實到每個類的具體實現上要真正能體現系統事物的本質特征,因為事物的本質特征只有一個,你的代碼越接近它,表示你的設計就是簡單明了, 越簡單明了,你的系統就越可靠。更多情況是,一個類并不能反應事物本質,需要多個類的組合協調,那么能夠正確使用合適的設計模式就稱為重中之重。
我們看一個具備好的架構設計的系統代碼時,基本看到的都是設計模式,寵物店(pet store)就是這樣的例子。或者可以這樣說,一個好的架構設計基本是由簡單明了的多個設計模式完成的。
3. 最靈活的拓展性:架構設計要具備靈活性 拓展性,這樣,用戶可以在你的架構上進行二次開發或更加具體的開發。
要具備靈活的拓展性,就要站在理論的高度去進行架構設計,比如現在工作流概念逐步流行,因為我們具體很多實踐項目中都有工作流的影子,工作流中有一個樹形結構權限設定的概念就對很多領域比較通用。
樹形結構是組織信息的基本形式,我們現在看到的網站或者ERP前臺都是以樹形菜單來組織功能的,那么我們在進行架構設計時,就可以將樹形結構和 功能分開設計,他們之間聯系可以通過樹形結構的節點link在一起,就象我們可以在圣誕樹的樹枝上掛各種小禮品一樣,這些小禮品就是我們要實現的各種功 能。
有了這個概念,通常比較難實現的用戶級別權限控制也有了思路,將具體用戶或組也是和樹形結構的節點link在一起,這樣就間接實現了用戶對相應功能的權限控制,有了這樣的基本設計方案的架構無疑具備很靈活的拓展性。
import java.security.*;
import java.io.*;
import java.util.*;
import java.security.*;
import java.security.cert.*;
import sun.security.x509.*
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
二:從文件中讀取證書
用keytool將.keystore中的證書寫入文件中,然后從該文件中讀取證書信息
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in=new FileInputStream("out.csr");
Certificate c=cf.generateCertificate(in);
String s=c.toString();
三:從密鑰庫中直接讀取證書
String pass="123456";
FileInputStream in=new FileInputStream(".keystore");
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,pass.toCharArray());
java.security.cert.Certificate c=ks.getCertificate(alias);//alias為條目的別名
四:JAVA程序中顯示證書指定信息
System.out.println("輸出證書信息:\n"+c.toString());
System.out.println("版本號:"+t.getVersion());
System.out.println("序列號:"+t.getSerialNumber().toString(16));
System.out.println("主體名:"+t.getSubjectDN());
System.out.println("簽發者:"+t.getIssuerDN());
System.out.println("有效期:"+t.getNotBefore());
System.out.println("簽名算法:"+t.getSigAlgName());
byte [] sig=t.getSignature();//簽名值
PublicKey pk=t.getPublicKey();
byte [] pkenc=pk.getEncoded();
System.out.println("公鑰");
for(int i=0;i<pkenc.length;i++)System.out.print(pkenc[i]+",");
五:JAVA程序列出密鑰庫所有條目
String pass="123456";
FileInputStream in=new FileInputStream(".keystore");
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,pass.toCharArray());
Enumeration e=ks.aliases();
while(e.hasMoreElements())
java.security.cert.Certificate c=ks.getCertificate((String)e.nextElement());
六:JAVA程序修改密鑰庫口令
String oldpass="123456";
String newpass="654321";
FileInputStream in=new FileInputStream(".keystore");
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,oldpass.toCharArray());
in.close();
FileOutputStream output=new FileOutputStream(".keystore");
ks.store(output,newpass.toCharArray());
output.close();
七:JAVA程序修改密鑰庫條目的口令及添加條目
FileInputStream in=new FileInputStream(".keystore");
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,storepass.toCharArray());
Certificate [] cchain=ks.getCertificate(alias);獲取別名對應條目的證書鏈
PrivateKey pk=(PrivateKey)ks.getKey(alias,oldkeypass.toCharArray());獲取別名對應條目的私鑰
ks.setKeyEntry(alias,pk,newkeypass.toCharArray(),cchain);向密鑰庫中添加條目
第一個參數指定所添加條目的別名,假如使用已存在別名將覆蓋已存在條目,使用新別名將增加一個新條目,第二個參數為條目的私鑰,第三個為設置的新口令,第四個為該私鑰的公鑰的證書鏈
FileOutputStream output=new FileOutputStream("another");
ks.store(output,storepass.toCharArray())將keystore對象內容寫入新文件
八:JAVA程序檢驗別名和刪除條目
FileInputStream in=new FileInputStream(".keystore");
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,storepass.toCharArray());
ks.containsAlias("sage");檢驗條目是否在密鑰庫中,存在返回true
ks.deleteEntry("sage");刪除別名對應的條目
FileOutputStream output=new FileOutputStream(".keystore");
ks.store(output,storepass.toCharArray())將keystore對象內容寫入文件,條目刪除成功
九:JAVA程序簽發數字證書
(1)從密鑰庫中讀取CA的證書
FileInputStream in=new FileInputStream(".keystore");
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,storepass.toCharArray());
java.security.cert.Certificate c1=ks.getCertificate("caroot");
(2)從密鑰庫中讀取CA的私鑰
PrivateKey caprk=(PrivateKey)ks.getKey(alias,cakeypass.toCharArray());
(3)從CA的證書中提取簽發者的信息
byte[] encod1=c1.getEncoded(); 提取CA證書的編碼
X509CertImpl cimp1=new X509CertImpl(encod1); 用該編碼創建X509CertImpl類型對象
X509CertInfo cinfo1=(X509CertInfo)cimp1.get(X509CertImpl.NAME+"."+X509CertImpl.INFO); 獲取X509CertInfo對象
X500Name issuer=(X500Name)cinfo1.get(X509CertInfo.SUBJECT+"."+CertificateIssuerName.DN_NAME); 獲取X509Name類型的簽發者信息
(4)獲取待簽發的證書
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in2=new FileInputStream("user.csr");
java.security.cert.Certificate c2=cf.generateCertificate(in);
(5)從待簽發的證書中提取證書信息
byte [] encod2=c2.getEncoded();
X509CertImpl cimp2=new X509CertImpl(encod2); 用該編碼創建X509CertImpl類型對象
X509CertInfo cinfo2=(X509CertInfo)cimp2.get(X509CertImpl.NAME+"."+X509CertImpl.INFO); 獲取X509CertInfo對象
(6)設置新證書有效期
Date begindate=new Date(); 獲取當前時間
Date enddate=new Date(begindate.getTime()+3000*24*60*60*1000L); 有效期為3000天
CertificateValidity cv=new CertificateValidity(begindate,enddate); 創建對象
cinfo2.set(X509CertInfo.VALIDITY,cv); 設置有效期
(7)設置新證書序列號
int sn=(int)(begindate.getTime()/1000); 以當前時間為序列號
CertificateSerialNumber csn=new CertificateSerialNumber(sn);
cinfo2.set(X509CertInfo.SERIAL_NUMBER,csn);
(8)設置新證書簽發者
cinfo2.set(X509CertInfo.ISSUER+"."+CertificateIssuerName.DN_NAME,issuer);應用第三步的結果
(9)設置新證書簽名算法信息
AlgorithmId algorithm=new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
cinfo2.set(CertificateAlgorithmId.NAME+"."+CertificateAlgorithmId.ALGORITHM,algorithm);
(10)創建證書并使用CA的私鑰對其簽名
X509CertImpl newcert=new X509CertImpl(cinfo2);
newcert.sign(caprk,"MD5WithRSA"); 使用CA私鑰對其簽名
(11)將新證書寫入密鑰庫
ks.setCertificateEntry("lf_signed",newcert);
FileOutputStream out=new FileOutputStream("newstore");
ks.store(out,"newpass".toCharArray()); 這里是寫入了新的密鑰庫,也可以使用第七條來增加條目
十:數字證書的檢驗
(1)驗證證書的有效期
(a)獲取X509Certificate類型對象
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in1=new FileInputStream("aa.crt");
java.security.cert.Certificate c1=cf.generateCertificate(in1);
X509Certificate t=(X509Certificate)c1;
in2.close();
(b)獲取日期
Date TimeNow=new Date();
(c)檢驗有效性
try{
t.checkValidity(TimeNow);
System.out.println("OK");
}catch(CertificateExpiredException e){ //過期
System.out.println("Expired");
System.out.println(e.getMessage());
}catch((CertificateNotYetValidException e){ //尚未生效
System.out.println("Too early");
System.out.println(e.getMessage());}
(2)驗證證書簽名的有效性
(a)獲取CA證書
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in2=new FileInputStream("caroot.crt");
java.security.cert.Certificate cac=cf.generateCertificate(in2);
in2.close();
(c)獲取CA的公鑰
PublicKey pbk=cac.getPublicKey();
(b)獲取待檢驗的證書(上步已經獲取了,就是C1)
(c)檢驗證書
boolean pass=false;
try{
c1.verify(pbk);
pass=true;
}catch(Exception e){
pass=false;
System.out.println(e);
}一、功能:使用crontab命令裝載cron進程所需要的crontab文件。
格式:
格式1:crontab [-u user] [-l|-r|-e]
格式2:crontab [-u user] filename
其中:
-u user: 修改指定用戶的crontab文件。如果不指定該選項,crontab將默認為是操作者本人的crontab。
-l:在標準輸出上顯示當前的crontab任務。
-r:刪除當前的crontab任務。
-e:使用環境變量指定的編輯器編輯crontab文件。當結束編輯離開時,編輯后的文件將自動安裝。
filename:是一個crontab文件的來源文件
crontab文件的來源文件
crontab文件的來源文件存在的形式
一個符合語法規則的純文本文件,使用第2種格式的crontab命令裝載
使用第2種格式的crontab命令時,它是一個文本編輯器(如vi)的臨時文件,編輯結束自動裝載
crontab文件的來源文件的格式
每一行格式為:
分< >時< >日< >月< >星期< >要運行的命令
minute hour day-of-month month-of-year day-of-week [username] commands
其中:
minute:一小時中的哪一分鐘(0~59)
hour:一天中的哪個小時(0~23)
day-of-month:一月中的哪一天(1~31)
month-of-year:一年中的哪一月(1~12)
day-of-week:一周中的哪一天(0~6)
username:以指定的用戶身份執行commands
commands:執行的命令(可以是多行命令或者是腳本調用)
五個時間字段的語法說明
不能為空,可以使用統配符*表示任何時間。
可以指定多個值,它們之間用逗號間隔。例如:1,3,7。
可以指定時間段,用減號間隔。例如:0-6。
可以用/n表示步長。例如:8-18/2表示時間序列8,10,12,14,16,18
中國移動、中國聯通推行的GPRS網絡、CDMA網絡已覆蓋大量的區域,通過無線網絡實現數據傳輸成為可能。無線Modem采用GPRS、CDMA
模塊通過中國移動、中國聯通的GPRS、CDMA網絡進行數據傳輸,并通TCP/IP協議進行數據封包,可靈活地實現多種設備接入,工程安裝簡單,在工業
現場數據傳輸的應用中,能很好的解決偏遠無網絡無電話線路地區的數據傳輸的難題。同傳統的數傳電臺想比較,更具有簡便性、靈活性、易操作性,同時還降低了
成本,無線Modem傳輸方案是現代化工業現場數據傳輸最好的選擇方案。
目前中國移動、中國聯通提供的GPRS網絡、CDMA網絡的數據傳輸帶寬在40Kbps左右,且受帶寬的限制,數據采集方案最好采用于主動告警、數據輪巡
采集、告警主動回叫等對傳輸帶寬占用較少的采集方式。同時考慮對前置機實時采集方案的支持,無線Modem傳輸方案只能作為目前傳輸方案的補充。
隨著無線通訊技術的不斷發展,無線傳輸數據帶寬將不斷提高,采用3G無線網絡,數據傳輸帶寬將達到2M,無線傳輸方案將逐漸成為監控傳輸組網的主要應用方案。
目前,由于GPRS和CDMA固有的特性,在各個領域中GPRS和CDMA的應用也越來越廣泛,但是關于傳輸中使用TCP/IP協議還是UDP協議,卻爭論很多。
這里先簡單的說一下TCP與UDP的區別:
1。基于連接與無連接
2。對系統資源的要求(TCP較多,UDP少)
3。UDP程序結構較簡單
4。流模式與數據報模式
5。TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證
另外結合GPRS網絡的情況具體的談一下他們的區別:
1。TCP傳輸存在一定的延時,大概是1600MS(移動提供),UDP響應速度稍微快一些。
2。TCP包頭結構
源端口16位
目標端口 16位
序列號 32位
回應序號 32位
TCP頭長度 4位
reserved 6位
控制代碼6位
窗口大小16位
偏移量16位
校驗和16位
選項 32位(可選)
這樣我們得出了TCP包頭的最小大小.就是20字節.
UDP包頭結構
源端口16位
目的端口16位
長度 16位
校驗和 16位
UDP的包小很多.確實如此.因為UDP是非可靠連接.設計初衷就是盡可能快的將數據包發送出去.所以UDP協
議顯得非常精簡.
3。GPRS網絡端口資源,UDP十分緊缺,變化很快;而TCP采用可靠鏈路傳輸,不存在端口變化的問題工業場合的應用一般都有以下特點,
1。要求時時傳輸,但也有一些場合是定時傳輸,總的來說在整個傳輸過程中要求服務器中心端和GPRS終端設備能相互的、時時的傳輸數據。
TCP
本身就是可靠鏈路傳輸,提供一個時時的雙向的傳輸通道,能很好的滿足工業現場傳輸的要求。但是GPRS網絡對TCP鏈路也存在一個限制:此條鏈路在長時間
(大概20分鐘左右,視具體情況而定)沒有數據流量,會自動降低此鏈路的優先級直至強制斷開此鏈路。所以在實際使用中也會采用心跳包(一般是一個字節的數
據)來維持此鏈路。
UDP由于自身特點,以及GPRS網絡UDP端口資源的有限性,在一段時間沒有數據流量后,端口容易改變,產生的影響就是從服
務器中心端向GPRS終端發送數據,GPRS終端接收不到。具體的原因就是移動網關從中作了中轉,需要隔一定時間給主機發UDP包來維持這個IP和端口
號,這樣主機就能主動給GPRS發UDP包了并且我在測試中發現,這個間隔時間很短,我在1多分鐘發一次UDP包才能夠維持,但是再長可能移動網關那邊就
要丟失這個端口了,此時如果主機想主動發數據給GPRS,那肯定是不行的了,只有GPRS終端設備再發一個UDP包過去,移動重新給你分配一個中轉IP和
端口,才能夠進行雙向通訊。
2。要求數據的丟包率較小。有些工業場合,例如電力、水務抄表,環保監測等等,不容許傳輸過程中的數據丟失或者最大限度的要求數據的可靠性。從這 一點來看,很顯然在無線數據傳輸過程中,TCP比UDP更能保證數據的完整性、可靠性,存在更小的丟包率。在實際測試中也是如此。以廈門桑榮科技有限公司 提供的GPRS終端設備為例:TCP的在千分之9,UDP的在千分之17左右。
3。要求降低費用。目前有很大部分GPRS設備的應用都是取代前期無線數傳電臺,除了使用范圍外,其考慮的主要問題就是費用。能降低費用當然都是大 家最愿意接受的。和費用直接相關的就是流量了,流量低,費用就低了。雖然TCP本身的包頭要比UDP多,但是UDP在實際應用中往往需要維護雙向通道,就 必須要通過大量的心跳包數據來維護端口資源。總的比較起來,UDP的實際流量要比TCP還要大。很多使用者在初期的時候并不了解UDP需要大量心跳包來維 持端口資源這個問題,往往都認為UDP要比TCP更節省流量,實際上這里存在著一個誤區。
4。在某些特定的應用場合,例如一些銀行的時時交互系統,對響應速度要求很高,此時數據傳輸頻率較快,不需要大量心跳包維持UDP端口資源,采用UDP就比較有利了。
5。在目前的1:N的傳輸模式中,既有多個GPRS終端設備往一個服務器中心傳輸數據,此時采用UDP會比TCP要好的多,因為UDP耗用更少的系 統資源。但是在實際應用中卻發現,很多用戶還是采用TCP的傳輸方式,建立二級中心1:A(1:N),即每一個分中心對應N/A臺設備,獨立處理數據,再 統一將數據傳送到主中心。這樣既能保證了傳輸過程中采用了TCP的傳輸協議,又能很好處理了中心服務器的多鏈路的系統耗用的問題。Siege(英文意思是圍攻)是一個壓力測試和評測工具,設計用于WEB開發這評估應用在壓力下的承受能力:可以根據配置對一個WEB站點進行多用戶的并發訪問,記錄每個用戶所有請求過程的相應時間,并在一定數量的并發訪問下重復進行。
最早使用的壓力測試工具是apache的ab(apache benchmark),apache ab做重復壓力測試不錯,但是每次只能測試一個鏈接,如何測試一組鏈接(比如從日志中導出的1個小時的日志,做真實壓力測試),后來找到了這個:
Siege是一個壓力測試和評測工具,設計用于WEB開發這評估應用在壓力下的承受能力:可以根據配置對一個WEB站點進行多用戶的并發訪問,記錄每個用戶所有請求過程的相應時間,并在一定數量的并發訪問下重復進行。
SIEGE is an http regressive testing and benchmarking utility. It was designed to let web developers measure the performance of their code under duress, to see how it will stand up to load on the internet. It lets the user hit a webserver with a configurable number of concurrent simulated users. Those users place the webserver "under siege." The duration of the siege is measured in transactions, the sum of simulated users and the number of times each simulated user repeats the process of hitting the server. Thus 20 concurrent users 50 times is 1000 transactions, the length of the test.
下載/安裝
Siege時一個開放源代碼項目:http://www.joedog.org
下載:
wget ftp://sid.joedog.org/pub/siege/siege-latest.tar.gz
安裝:
%./configure ; make
#make install
siege包含了一組壓力測試工具:
SIEGE (1) Siege是一個HTTP壓力測試和評測工具.
使用樣例:
任務列表:www.chedong.com.url文件
http://www.chedong.com/tech/
http://www.chedong.com/tech/acdsee.html
http://www.chedong.com/tech/ant.html
http://www.chedong.com/tech/apache_install.html
http://www.chedong.com/tech/awstats.html
http://www.chedong.com/tech/cache.html
http://www.chedong.com/tech/click.html
http://www.chedong.com/tech/cms.html
http://www.chedong.com/tech/compress.html
http://www.chedong.com/tech/cvs_card.html
http://www.chedong.com/tech/default.html
http://www.chedong.com/tech/dev.html
http://www.chedong.com/tech/gnu.html
....
siege -c 20 -r 2 -f www.chedong.com.url
參數說明:
-c 20 并發20個用戶
-r 2 重復循環2次
-f www.chedong.com.url 任務列表:URL列表
輸出樣例:
** Siege 2.59
** Preparing 20 concurrent users for battle. 這次“戰斗”準備了20個并發用戶
The server is now under siege.. done. 服務在“圍攻”測試中:
Transactions: 40 hits 完成40次處理
Availability: 100.00 % 成功率
Elapsed time: 7.67 secs 總共用時
Data transferred: 877340 bytes 共數據傳輸:877340字節
Response time: 1.65 secs 相應用時1.65秒:顯示網絡連接的速度
Transaction rate: 5.22 trans/sec 平均每秒完成5.22次處理:表示服務器后臺處理的速度
Throughput: 114385.92 bytes/sec 平均每秒傳送數據:114385.92字節
Concurrency: 8.59 最高并發數 8.59
Successful transactions: 40 成功處理次數
Failed transactions: 0 失敗處理次數
注意:由于速度很快,可能會達不到并發速度很高就已經完成。Response time顯示的是測試機器和被測試服務器之間網絡鏈接狀況。Transaction rate則表示服務器端任務處理的完成速度。
輔助工具:
增量壓力測試:
為了方便增量壓力測試,siege還包含了一些輔助工具:
bombardment (1)
是一個輔助工具:用于按照增量用戶壓力測試:
使用樣例:
bombardment urlfile.txt 5 3 4 1
初始化URL列表:urlfile.txt
初始化為:5個用戶
每次增加:3個用戶
運行:4次
每個客戶端之間的延遲為:1秒
輸出成CSV格式:
siege2csv.pl (1)
siege2csv.pl將bombardment的輸出變成CSV格式:
Time Data Transferred Response Time Transaction Rate Throughput Concurrency Code 200 (note that this is horribly broken.)
242 60.22 603064 0.02 4.02 10014.35 0.08
605 59.98 1507660 0.01 10.09 25136.05 0.12
938 59.98 2337496 0.02 15.64 38971.26 0.26
1157 60 2883244 0.04 19.28 48054.07 0.78