开源网站流量统计系统Piwik源码分析——后台处理(二)
在第一篇文章中,重点介绍了脚本需要搜集的数据,而本篇主要介绍的是服务器端如何处理客户端发送过来的请求和参数。
一、设备信息检测
通过分析User-Agent请求首部(如下图红线框出的部分),可以得到相关的设备信息。

Piwik系统专门有一套代码用来分析代理信息,还独立了出来,叫做DeviceDetector。它有一个专门的demo页面,可以展示其功能,点进去后可以看到下图中的内容。

它能检测出浏览器名称、浏览器的渲染引擎、浏览器的版本、设备品牌(例如HTC、Apple、HP等)、设备型号(例如iPad、Nexus 5、Galaxy S5等)、设备类别(例如desktop、smartphone、tablet等),这6类数据中的可供选择的关键字,可以参考“List of segments”或插件的“readme”。顺便说一下,Piwik还能获取到访客的定位信息,在“List of segments”中,列举出了城市、经纬度等信息,其原理暂时还没研究。
Piwik为大部分设备信息的关键字配备了一个icon图标,所有的icon图标被放置在“plugins\Morpheus\icons”中,包括浏览器、设备、国旗、操作系统等,下图截取的是浏览器中的部分图标。

二、IP地址
在Piwik系统的后台设置中,可以选择IP地址的获取方式(如下图所示)。在官方博客的一篇《Geo Locate your visitors》博文中提到,3.5版本后可以在系统中嵌入MaxMind公司提供的IP地理定位服务(GeoIP2)。

下面是一张看官方的产品介绍表,从描述中可看出这是一项非常厉害的服务。不过需要注意的是,这是一项付费服务。

三、日志数据和归档数据
在官方发布的说明文档《How Matomo (formerly Piwik) Works》中提到,在Piwik中有两种数据类型:日志数据和归档数据。日志数据(Log Data)是一种原始分析数据,从客户端发送过来的参数就是日志数据,刚刚设备检测到的信息也是日志数据,还有其它的一些日志数据的来源,暂时还没细究。由于日志数据非常巨大,因此不能直接生成最终用户可看的报告,得使用归档数据来生成报告。归档数据(Archive Data)是以日志数据为基础而构建出来的,它是一种被缓存并且可用于生成报告的聚合分析数据。
日志数据会通过“core\Piwik\Tracker\Visit.php”中的方法保存到数据库中,其中核心的方法如下所示,注释中也强调了该方法中的内容是处理请求的主要逻辑。该方法涉及到了很多对象,以及对象的方法,错综复杂,我自己也没有研究透,只是利用PHPStorm编辑器自动索引,查找出了一些关联,具体细节还有待考证。
/**
* Main algorithm to handle the visit.
*
* Once we have the visitor information, we have to determine if the visit is a new or a known visit.
*
* 1) When the last action was done more than 30min ago,
* or if the visitor is new, then this is a new visit.
*
* 2) If the last action is less than 30min ago, then the same visit is going on.
* Because the visit goes on, we can get the time spent during the last action.
*
* NB:
* - In the case of a new visit, then the time spent
* during the last action of the previous visit is unknown.
*
* - In the case of a new visit but with a known visitor,
* we can set the 'returning visitor' flag.
*
* In all the cases we set a cookie to the visitor with the new information.
*/
public function handle() {
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::manipulateRequest()...");
$processor->manipulateRequest($this->request);
}
$this->visitProperties = new VisitProperties();
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::processRequestParams()...");
$abort = $processor->processRequestParams($this->visitProperties, $this->request);
if ($abort) {
Common::printDebug("-> aborting due to processRequestParams method");
return;
}
}
$isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit');
if (!$isNewVisit) {
$isNewVisit = $this->triggerPredicateHookOnDimensions($this->getAllVisitDimensions() , 'shouldForceNewVisit');
$this->request->setMetadata('CoreHome', 'isNewVisit', $isNewVisit);
}
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::afterRequestProcessed()...");
$abort = $processor->afterRequestProcessed($this->visitProperties, $this->request);
if ($abort) {
Common::printDebug("-> aborting due to afterRequestProcessed method");
return;
}
}
$isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit');
// Known visit when:
// ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor
// OR
// - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor()
// )
// AND
// - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit()
if (!$isNewVisit) {
try {
$this->handleExistingVisit($this->request->getMetadata('Goals', 'visitIsConverted'));
}
catch(VisitorNotFoundInDb $e) {
$this->request->setMetadata('CoreHome', 'visitorNotFoundInDb', true); // TODO: perhaps we should just abort here? }
}
// New visit when:
// - the visitor has the Piwik cookie but the last action was performed more than 30 min ago @see isLastActionInTheSameVisit()
// - the visitor doesn't have the Piwik cookie, and couldn't be matched in @see recognizeTheVisitor()
// - the visitor does have the Piwik cookie but the idcookie and idvisit found in the cookie didn't match to any existing visit in the DB
if ($isNewVisit) {
$this->handleNewVisit($this->request->getMetadata('Goals', 'visitIsConverted'));
}
// update the cookie with the new visit information
$this->request->setThirdPartyCookie($this->request->getVisitorIdForThirdPartyCookie());
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::recordLogs()...");
$processor->recordLogs($this->visitProperties, $this->request);
}
$this->markArchivedReportsAsInvalidIfArchiveAlreadyFinished();
}
最后了解一下Piwik的数据库设计,此处只分析与日志数据和归档数据有关的数据表。官方的说明文档曾介绍,日志数据有5张相关的数据表,我对于表的内在含义还比较模糊,因此下面所列的描述还不是很清晰。
(1)log_visit:每次访问都会生成一条访问者记录,表中的字段可参考“Visits”。
(2)log_action:网站上的访问和操作类型(例如特定URL、网页标题),可分析出访问者感兴趣的页面,表中的字段可参考“Action Types”。
(3)log_link_visit_action:访问者在浏览期间执行的操作,表中的字段可参考“Visit Actions”。
(4)log_conversion:访问期间发生的转化(与目标相符的操作),表中的字段可参考“Conversions”。
(5)log_conversion_item:与电子商务相关的信息,表中的字段可参考“Ecommerce items”。
归档数据的表有两种前缀,分别是“archive_numeric_”和“archive_blob_”,表的字段可参考“Archive data”。通过对字段的观察可知,两种最大的不同就是value字段的数据类型。archive_numeric_* 表中的value能储存数值(数据类型是Double),而archive_blob_* 表中的value能储存出数字以外的其他任何数据(数据类型是Blob)。
两种表都是动态生成的,因此前缀的后面都用“*”表示。生成规则可按年、月、周、天或自定义日期范围,不设置的话,默认是按月计算,例如archive_numeric_2018_09、archive_blob_2018_09。
参考资料:
How Matomo (formerly Piwik) Works
开源网站流量统计系统Piwik源码分析——后台处理(二)的更多相关文章
- 开源网站流量统计系统Piwik源码分析——参数统计(一)
Piwik现已改名为Matomo,这是一套国外著名的开源网站统计系统,类似于百度统计.Google Analytics等系统.最大的区别就是可以看到其中的源码,这正合我意.因为我一直对统计的系统很好奇 ...
- 开源网站访问统计系统Piwik
http://www.piwik.cn/ http://www.piwik.org/ Piwik 是一套基于 Php+MySQL 技术构建,能够与 Google Analytics 相媲美的开源网站访 ...
- 网站流量统计系统 phpMyVisites
phpMyVisites是一个网站流量统计系统,它能够提供非常详细的统计报告和高级图形报表.phpMyVisites不是一个Apache log分析工具,它建有自己的log.它的特点包括: 安装部署: ...
- 手机自动化测试:appium源码分析之bootstrap二
手机自动化测试:appium源码分析之bootstrap二 在bootstrap项目中的io.appium.android.bootstrap.handler包中的类都是对应的指令类, priva ...
- [软件测试]网站压测工具Webbench源码分析
一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...
- 【精】EOS智能合约:system系统合约源码分析
系统合约在链启动阶段就会被部署,是因为系统合约赋予了EOS链资源.命名拍卖.基础数据准备.生产者信息.投票等能力.本篇文章将会从源码角度详细研究system合约. 关键字:EOS,eosio.syst ...
- ABP源码分析三十二:ABP.SignalR
Realtime Realtime是ABP底层模块提供的功能,用于管理在线用户.它是使用SignalR实现给在线用户发送通知的功能的前提 IOnlineClient/OnlineClient: 封装在 ...
- Thinkphp源码分析系列(二)–引导类
在上一章我们说到,ThinkPHP.php在设置完框架所需要的变量和调教好环境后,在最后调用了 Think\Think::start(); 即Think命名空间中的Think类的静态方法start ...
- Netty源码分析 (十二)----- 心跳服务之 IdleStateHandler 源码分析
什么是心跳机制? 心跳说的是在客户端和服务端在互相建立ESTABLISH状态的时候,如何通过发送一个最简单的包来保持连接的存活,还有监控另一边服务的可用性等. 心跳包的作用 保活Q:为什么说心跳机制能 ...
随机推荐
- 原子性 CAS算法
一. i++ 的原子性问题 1.问题的引入: i++ 的实际操作分为三个步骤:读--改--写 实现线程,代码如下: public class AtomicDemo implements Runnabl ...
- Android通过手机搭建服务器,WIFI建立热点实现C/S聊天室通信功能
应用效果图: 客户端 ...
- org.apache.hadoop.security.AccessControlException
在hdfs集群上,需要向Hdfs写入文件,控制台会输出以下错误信息: Caused by: org.apache.hadoop.ipc.RemoteException(org.apache.hadoo ...
- Linux 使用 mail 发送邮件
ubuntu 需要安装 mailutils sudo apt-get install mailutils
- 【并发编程】IO模型
一.要点回顾 为了更好地了解IO模型,我们需要先回顾下几个概念:同步.异步.阻塞.非阻塞 同步: 一个进程在执行某个任务时,另外一个进程必须等待其执行完毕,才能继续执行.就是在发出一个功能调用时,在没 ...
- Java输入输出流详解
通过数据流.序列化和文件系统提供系统输入和输出. Java把这些不同来源和目标的数据都统一抽象为数据流.Java语言的输入输出功能是十分强大而灵活的,美中不足的是看上去输入输出的代码并不是很简洁,因为 ...
- Docker构建文件
构建文件 创建Dockerfile touch Dockerfile 编辑Dockerfile vim Dockerfile #基于java8版本构建 FROM java:8 #挂载日志目录 VOLU ...
- fscanf_s与scanf_s的宽度参数与缓冲区参数分析
fscanf_s函数 在文件操作中经常会用到fscanf这个函数,但是在VC和VS中会有警告 意思是编译器觉得fscanf不安全,叫你考虑用一下fscanf_s这个函数来代替fscanf,fscanf ...
- redis 分布式读写锁
http://zhangtielei.com/posts/blog-redlock-reasoning.html 链接里这篇 blog 讨论了 redis 分布式锁的实现以及安全性 我要参考 基于单R ...
- 开源性能测试工具Locust使用篇(一)
1. 环境准备 安装python3.6 ,安装步骤略 pip install locust 安装完成后使用locust -V检查 2.locust使用,先编辑一个简单的load_test.py的脚本 ...