2013년 7월 4일 목요일

Apache Access Log + Mongo DB + Sigma.js

Apache access log를 뭔가??? 비줠 하게 표현하는 ...
이건 해놓기는 했는데 뭔지 좀 병맛이 나......

1. Apache Access Log를 Mongo DB에 넣는다 (ETL 과정)
ETL어떻게 손으로 하나...꽁짜 ETL로 돌립니다.
Talend Open Studio 라는 녀석입니다. (for bigdata 버젼을 받으면  mongodb, hadoop Adapter 들이 제공됩니다.


대충 웹서버 1시간 로그를 15대에서 줏어와서 밀어넣으니 9000만건 정도 들어가내요


2. MapReduce로 MongnDB에서 IP기준으로 이런 저런 데이터를 뽑아봅다.

- 브라우져 점유울 높은놈  MR 하기 
var map_function = function() {
 var key = this.agent;

 if(key != null){
     if(key.indexOf("MSIE") > -1){
        if(key.indexOf("compatible; MSIE 5.0") > -1){
        key = "IE5_";
        }else if(key.indexOf("compatible; MSIE 9.0") > -1 || key.indexOf("compatible; 9.0") > -1){
        key = "IE9_";
        }else if(key.indexOf("compatible; MSIE 10.0") > -1 || key.indexOf("compatible; 10.0") > -1){
        key = "IE10_";
        }else if(key.indexOf("compatible; MSIE 8.0") > -1 || key.indexOf("compatible; 8.0") > -1){
        key = "IE8_";
        }else if(key.indexOf("compatible; MSIE 6.0") > -1 || key.indexOf("compatible; 6.0") > -1){
        key = "IE6_";
        }else if(key.indexOf("compatible; MSIE 7.0") > -1 || key.indexOf("compatible; 7.0") > -1){
        key = "IE7_";
        }else if(key.indexOf("compatible; MSIE 7.7") > -1 || key.indexOf("compatible; 7.7") > -1){
        key = "IE Mobile 7.7_";
        }else{
        key = "IE_etc_";
        }
    }else if(key.toLowerCase().indexOf("winhttp") > -1){
    key = "WinHttp Application_";
    }else if(key.indexOf("Firefox") > -1){
    key = "Firefox_";
     }else if(key.indexOf("Chrome") > -1){
    key = "Chrome_";
     }else if(key.indexOf("Opera") > -1){
    key = "Opera_";
      }else if(key.indexOf("Chrome") < 0 && key.indexOf("Safari") > -1 ){
    key = "Safari_";
      }else if(key.indexOf("Macintosh") > -1 || key.indexOf("iPad") > -1 || key.indexOf("iPhone") > -1){
    key = "Safari_";
     }else if(key.indexOf("Android") > -1){
    key = "Android_";
     }else if(key.indexOf("Java") > -1){
    key = "Java Application_";
     }else if(key.indexOf("Python") > -1){
    key = "Python Application_";
     }else if(key.indexOf("Snoopy") > -1){
    key = "PHP Snoopy Application_";
     }else if(key.indexOf("CoolNovo") > -1){
    key = "CoolNovo_";
     }else if(key.indexOf("Apache") > -1 || key.indexOf("Jakarta") > -1){
    key = "Java HttpClient Application_";
     }else if(key.indexOf("CFNetwork") > -1){
    key = "CFNetwork Application_";
     }else if(key.toLowerCase().indexOf("bot") > -1){
    key = "Bot Crawler_";
     }else if(key.indexOf("Embedly") > -1){
    key = "Embedly Application_";
     }else if(key.indexOf("AdobeAIR") > -1){
    key = "AdobeAIR_";
     }else if(key.indexOf("Flash") > -1){
    key = "Shockwave Flash_";
     }else if(key.indexOf("-") > -1 || key == "null"){
    key = "Bad User Agent_";
    }else{
    key = "etc";
    }
  }
  var value ={count:1};
  emit(key,value);
};

var reduce_function = function(key,value){
  var reduceObj = {
             count:0
             };
  value.forEach(function(value){
    reduceObj.count += value.count;
    }
  );
 
    return reduceObj;
};


db.log_system_weblog.mapReduce(
  map_function,
  reduce_function,
  {
  out : {replace : "mr_system_agent", db : "mrdb"},
  query : {date:{ $gt: "2013-05-13 13:00:00", $lt: "2013-05-13 13:59:00" }}
  }
);

- IP별로 전채 송수신 바이트, 응답시간의총합, 평균응답시간, 호출수 를 구한다. 
// map으로 사용할 데이터를 지정한다.
var map_function = function() {
 var key = this.host;
 var value ={
             hostip : this.host,
             bytes:this.bytes,
             resptime:this.resptime,
             count:1
             };
emit(key,value);
};
//reduce 할 객체를 지정하고 각 항목에 값을 연산하여 집어 넣는다.
var reduce_function = function(key,value){
  var reduceObj = {
             hostip : key,
             bytes:0,
             resptime:0,
             respavg:0,
             count:0
             };
  value.forEach(function(value){
    reduceObj.bytes += value.bytes;
    reduceObj.resptime += value.resptime;
    reduceObj.count += value.count;
    }
  );
    return reduceObj;
};
//다 들어갔으면 평균값 연산을 위하여 final 작업을 하여 집어 넣는다.
var finalize_function = function(key,value){
  if(value.count > 0){
    value.respavg = value.resptime / value.count;
  }
 return value;
};
//MR을 수행한다. 이때 MR 조건은 여기다가 query로 지정한다.
db.log_system_weblog.mapReduce(
  map_function,
  reduce_function,
  { out : "mr_system_weblog",
    query :  {host:{$ne:null}},
    finalize : finalize_function
  }
 );
다른DB에 쓰쓸 경우 아래 참고
db.log_system_weblog.mapReduce(
  map_function,
  reduce_function,
  {
  out : {replace : "mr_system_weblog", db : "mrdb"},
  query : {date:{ $gt: "2013-05-13 13:00:00", $lt: "2013-05-13 13:05:00" }},
  finalize : finalize_function
  }
);
/////////20130530 작업버젼
IP별로 전송용량, 응답시간평균, IP가 Access한 URL List 전체를 포함한 MR collection을 만든다.
수행할때 마다 기존의 Collection은 Replace(제거후생성)한다.
db는 별도 DB에 생성됨
var map_function = function() {
 var key = this.host;
 var value ={
             hostip:this.host,
             bytes:this.bytes,
             resptime:this.resptime,
             url:this.file,
             count:1
             };
emit(key,value);
};

var reduce_function = function(key,value){
  var reduceObj = {
             hostip : key,
             bytes:0,
             resptime:0,
             respavg:0,
             url : "",
             count:0
             };
  value.forEach(function(value){
    reduceObj.bytes += value.bytes;
    reduceObj.resptime += value.resptime;
    reduceObj.count += value.count;
    reduceObj.url += ","+value.url;
    }
  );
 
    return reduceObj;
};
var finalize_function = function(key,value){
  if(value.count > 0){
    value.respavg = value.resptime / value.count;
  }
  if(value.count > 1){
      value.url = value.url.substring(1);
    }
 return value;
};
db.log_system_weblog.mapReduce(
  map_function,
  reduce_function,
  {
  out : {replace : "mr_system_weblog", db : "mrdb"},
  query : {date:{ $gt: "2013-05-13 13:00:00", $lt: "2013-05-13 13:59:00" }},
  finalize : finalize_function
  }
);
===============
shard 환경에서  출력만들때

db.log_system_weblog.mapReduce(
  map_function,
  reduce_function,
  {
  out : {replace : "mr_system_weblog", db : "mrdb", sharded : true},
  query : {date:{ $gt: "2013-05-13 13:00:00", $lt: "2013-05-13 13:59:00" }},
  finalize : finalize_function
  }
);

3. 이걸 비줠하게 표현하기 위해서 나는 gefx 파일 포멧으로 생성 했음

private static void makeTopProductGexf(AggregationOutput output) {
System.out.println("make GEXF");
 
//XML 구조를 생성한다.
// 파일 만들기
Gexf gexf = new GexfImpl();
Calendar date = Calendar.getInstance();
//gexf.setVariant("xmlns=\"http://www.gephi.org/gexf\"");
//확장 Namespace를 사용하기 위해서는 gexf.setVisualization(true); 을 해야 NS를 조회한다.
gexf.setVisualization(true);
gexf.getMetadata()
.setLastModified(date.getTime())
.setCreator("kwon")
.setDescription("Web Traffic Relation");


Graph graph = gexf.getGraph();
graph.
setDefaultEdgeType(EdgeType.UNDIRECTED)
.setMode(Mode.DYNAMIC)
.setTimeType(TimeFormat.XSDDATETIME);
AttributeList attrList = new AttributeListImpl(AttributeClass.NODE);
graph.getAttributeLists().add(attrList);
Attribute attUrl = attrList.createAttribute("URL", AttributeType.STRING, "url");
Attribute attCount = attrList.createAttribute("request cnt", AttributeType.INTEGER, "count")
.setDefaultValue("true");
// 출력결과를 돌리면서 노드를 생성한다.
Iterator<DBObject> it = output.results().iterator();
int cnt = 0;
Color color0 = new ColorImpl(255,51,51);
Color color1 = new ColorImpl(102,255,102);
Color color2 = new ColorImpl(255,153,153);
Color color3 = new ColorImpl(153,255,255);
Color color4 = new ColorImpl(153,255,0);
float size;
while(it.hasNext()){
DBObject obj = it.next();
String url = obj.get("_id").toString();
String count = obj.get("count").toString();
// 제품 번호 없는 놈은 패스
int idx = url.indexOf("prdNo");
if( idx < 0 ){
continue;
}
/* idx = url.indexOf("prdNo=");
if(idx > -1){
url = url.substring(idx);
idx = url.indexOf("&");
// prdno 뒤 잘라내기
if(idx > -1){
url= url.substring(0, idx);
}
// = 짤라내기
idx = url.indexOf("=");
if(idx > -1){
url= url.substring(idx+1);
}
}*/
//System.out.println("url: "+url + "count : "+count);
Node  node = graph.createNode(String.valueOf(cnt));
node.setLabel(url);
node.getAttributeValues().addValue(attUrl, url);
node.getAttributeValues().addValue(attCount, count);
// 한개의 노드 사이즈를 호출한 양에 따라 다르게 표현한다.
size = Float.parseFloat(count)/10;
node.setSize(size);
// 사이즈 군에 따라 색을 통일 지어 표현한다.
if(size < 0.5){
node.setColor(color3);
}else if(size < 1){
node.setColor(color2);
}else if(size < 1.5){
node.setColor(color1);
}else{
node.setColor(color4);
}
cnt++;
}
String filename = "topproduct.xml";
try {
writeXml(gexf,filename);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

4. 이녀석을 sigma.js로 불러서 읽어 들이면 대략 이렇게 나옴


sigma.js 에서 읽어들이는 html 코드는 아래

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>web Relation</title>
  <link rel="stylesheet" href="/css/bootstrap.min.css">
  <link rel="stylesheet" href="/css/bootstrap-responsive.min.css">
  <link rel="stylesheet" href="/css/style.css">
  <link rel="stylesheet" href="/css/prettify.css">
  
<script type="text/javascript" src="js/sigma.min.js"></script>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/sigma.parseGexf.js"></script>

<script type="text/javascript">

function init() {
 // Instanciate sigma.js and customize rendering :
 var sigInst = sigma.init(document.getElementById('sigma-example')).drawingProperties({
   defaultLabelColor: '#fff',
   defaultLabelSize: 14,
   defaultLabelBGColor: '#fff',
   defaultLabelHoverColor: '#000',
   labelThreshold: 6,
   defaultEdgeType: 'curve'
 }).graphProperties({
   minNodeSize: 0.5,
   maxNodeSize: 15,
   minEdgeSize: 1,
   maxEdgeSize: 1
 }).mouseProperties({
   maxRatio: 32
 });
 
 // Parse a GEXF encoded file to fill the graph
 // (requires "sigma.parseGexf.js" to be included)
 //sigInst.parseGexf('/data/arctic.gexf');
 //sigInst.parseGexf('data/arctic.xml');
 //sigInst.parseGexf('data/topip.xml');
 sigInst.parseGexf('data/topproduct.xml');
 // Draw the graph :
 sigInst.draw();

// 속성 출력 스크립트
 /**
   * Now, here is the code that shows the popup :
   */
  (function(){
    var popUp;
    // This function is used to generate the attributes list from the node attributes.
    // Since the graph comes from GEXF, the attibutes look like:
    // [
    //   { attr: 'Lorem', val: '42' },
    //   { attr: 'Ipsum', val: 'dolores' },
    //   ...
    //   { attr: 'Sit',   val: 'amet' }
    // ]
    function attributesToString(attr) {
      return '<ul>' +
        attr.map(function(o){
          return '<li>' + o.attr + ' : ' + o.val + '</li>';
        }).join('') +
        '</ul>';
    }
    function showNodeInfo(event) {
    //alert("야!!!!!")
      popUp && popUp.remove();
      var node;
      sigInst.iterNodes(function(n){
        node = n;
      },[event.content[0]]);
      //alert("1")
     popUp = $(
        '<div class="node-info-popup"></div>'
      ).append(
        // The GEXF parser stores all the attributes in an array named
        // 'attributes'. And since sigma.js does not recognize the key
        // 'attributes' (unlike the keys 'label', 'color', 'size' etc),
        // it stores it in the node 'attr' object :
        attributesToString( node['attr']['attributes'] )
      ).attr(
        'id',
        'node-info'+sigInst.getID()
      ).css({
        'display': 'inline-block',
        'border-radius': 3,
        'padding': 5,
        'background': '#fff',
        'color': '#000',
        'box-shadow': '0 0 4px #666',
        'position': 'absolute',
        'left': node.displayX,
        'top': node.displayY+15
      });
      //alert("2")
      $('ul',popUp).css('margin','0 0 0 20px');
       
      $('#sigma-example').append(popUp);
    }
    function hideNodeInfo(event) {
      popUp && popUp.remove();
      popUp = false;
    }
    sigInst.bind('overnodes',showNodeInfo).bind('outnodes',hideNodeInfo).draw();
  })();
}

if (document.addEventListener) {
 document.addEventListener("DOMContentLoaded", init, false);
} else {
 window.onload = init;
 alert('지원되지 않습니다.') 
}
</script>
<style type="text/css">
  /* sigma.js context : */
  .sigma-parent {
    position: relative;
    border-radius: 4px;
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    background: #222;
    height: 650px;
  }
  .sigma-expand {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
  }
  .buttons-container{
    padding-bottom: 8px;
    padding-top: 12px;
  }
</style>
</head>
<body>
<h1>Web Traffic Relation diagram-Top 100 Product</h1>
  <div class="span12 sigma-parent" id="sigma-example-parent">
    <div class="sigma-expand" id="sigma-example"></div>
  </div>

</body>
</html>

댓글 없음:

댓글 쓰기

본 블로그의 댓글은 검토후 등록됩니다.