/* * Copyright 2015 Evgeny Dolganov (evgenij.dolganov@gmail.com). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package och.comp.chats.stat; import static och.api.model.PropKey.*; import static och.comp.chats.common.StoreOps.*; import static och.util.DateUtil.*; import static och.util.FileUtil.*; import static och.util.Util.*; import static och.util.concurrent.AsyncListener.*; import static och.util.json.GsonUtil.*; import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import och.api.model.chat.ChatOperator; import och.api.model.chat.Message; import och.api.model.client.ClientInfo; import och.api.model.client.ClientSession; import och.comp.chats.model.Chat; import och.comp.chats.model.Chat.AddClientCommentRes; import och.service.props.Props; import och.util.concurrent.AsyncListener; import och.util.concurrent.ExecutorsUtil; import och.util.geoip.GeoIp; import och.util.timer.TimerExt; import org.apache.commons.logging.Log; public class AccsStat { public static final String STAT_DIR = "_stat"; private Log log = getLog(getClass()); private AsyncListener asyncListener; private File statRoot; private Props props; private boolean externalExecutor; private boolean externalTimer; private ExecutorService updateExecutor; private TimerExt timer; private ArrayList<StatsBuffer> buffers = new ArrayList<StatsBuffer>(); public AccsStat( File accsRoot, Props props, ExecutorService updateExecutor, AsyncListener asyncListener, TimerExt timer) { this.statRoot = new File(accsRoot, STAT_DIR); this.props = props; this.asyncListener = asyncListener; if(updateExecutor == null) updateExecutor = ExecutorsUtil.newSingleThreadExecutor("AccsStat-update-stat-thread"); else externalExecutor = true; this.updateExecutor = updateExecutor; if(props.getBoolVal(chats_useStat)){ buffers.add(new StatsBuffer(ONE_MINUTE, (map)-> rewriteStat(map, "_minute.stat"))); buffers.add(new StatsBuffer(ONE_MINUTE*30, (map)->rewriteStat(map, "_minute-30.stat"))); buffers.add(new StatsBuffer(ONE_HOUR, (map)->rewriteStat(map, "_hour.stat"))); buffers.add(new StatsBuffer(ONE_HOUR*6, (map)->rewriteStat(map, "_hour-6.stat"))); buffers.add(new StatsBuffer(ONE_DAY, (map)->rewriteDayStat(map))); if(timer == null) timer = new TimerExt("AccsStat-write-timer", false); else externalTimer = true; this.timer = timer; timer.tryScheduleAtFixedRate(() -> flushAndSaveStat(), props.getLongVal(chats_stat_Delay), props.getLongVal(chats_stat_Delay)); } } public void setGeoIp(GeoIp geoIp) { for(StatsBuffer buffer : buffers) buffer.setGeoIp(geoIp); } public void shutdown() { if(!externalExecutor) updateExecutor.shutdown(); if(!externalTimer) timer.cancel(); } public void flushAndSaveStat(){ if( ! props.getBoolVal(chats_useStat)) return; statRoot.mkdirs(); for(StatsBuffer buffer : buffers){ buffer.flushAndSaveIfNeed(); } } public void addFeedbackStatAsync(String accId, ClientInfo info) { asyncEvent((buffer)->buffer.addFeedbackStat(accId, info)); } public void createChatStatAsync(String accId, Chat chat, ClientSession client) { asyncEvent((buffer)->buffer.createChatStat(accId, chat, client)); } public void addClientMsgStatAsync(String accId, Chat chat, AddClientCommentRes result) { asyncEvent((buffer)-> buffer.addClientMsgStat(accId, chat, result)); } public void addOperatorStatAsync(String accId, Chat chat, ChatOperator operator) { asyncEvent((buffer)-> buffer.addOperatorStat(accId, chat, operator)); } public void addOperatorMsgStatAsync(String accId, Chat chat, Message result) { asyncEvent((buffer)-> buffer.addOperatorMsgStat(accId, chat, result)); } public void closeChatStatAsync(String accId, Chat chat) { asyncEvent((buffer)-> buffer.closeChatStat(accId, chat)); } private static interface StatsBufferEvent { void onEvent(StatsBuffer buffer); } private void asyncEvent(StatsBufferEvent e){ if( ! props.getBoolVal(chats_useStat)) return; Future<?> f = updateExecutor.submit(()-> { for(StatsBuffer buffer : buffers) { if(buffer.isFull()) continue; e.onEvent(buffer); } }); fireAsyncEvent(asyncListener, f); } private void rewriteDayStat(Map<String, AccStatData> map) { String date = formatDate(new Date(), DAY_FORMAT); rewriteStat(map, date+".stat"); } private void rewriteStat(Map<String, AccStatData> map, String fileName) { if(isEmpty(map)) return; for (Entry<String, AccStatData> entry : map.entrySet()) { AccStatData data = entry.getValue(); String accId = data.accId; File dir = getAccDir(statRoot, accId); dir.mkdirs(); try { data.prepareToSave(); String json = defaultGsonPrettyPrinting.toJson(data); writeFileUTF8(new File(dir, fileName), json); }catch(Exception e){ log.error("can't rewriteStat for "+fileName+": "+e); } } } }