source: lliurex-analytics-server/trunk/fuentes/lliurex-analytics-server/usr/lib/analytics-server/analytics/db.php @ 6767

Last change on this file since 6767 was 6767, checked in by mabarracus, 20 months ago

Fix test suite
Consolidation daemon ported to python3
Avoid daemon running with multiple instances
Fixes indent,sizes in fonts and graphics
System stats visualization page
Minimized javascript files
New cache for visualization graphs minimizing overloading and DoS attacks
Improved scheduler with clients sending 10+ results

File size: 19.8 KB
Line 
1<?php
2class Cache{
3    private $cache_file;
4    private $cache_dir;
5    private $data_file;
6    private $data;
7    private $cache_timeout=60*60;
8
9    function Cache(){
10        $this->cache_dir = sys_get_temp_dir();
11        $this->cache_file = "cache_analytics.db.tmp";
12        $this->datafile=$this->cache_dir.'/'.$this->cache_file;
13        $this->data = array();
14    }
15    function readFile(){
16        if (is_file($this->datafile) and is_writable($this->datafile)){
17            $this->filecontents = file_get_contents($this->datafile);
18        }else{
19            // do nothing or throw error
20            return false;
21        }
22        return true;
23    }
24    function newFile($data=array()){
25        // create new file
26        $obj=$data;
27        $json='';
28        try{
29            $json=json_encode($obj);
30        }catch(Exception $e){
31            return false;
32        }
33        file_put_contents($this->datafile,$json);
34    }
35    function parseJson(){
36        try{
37            $json=json_decode($this->filecontents,true);
38            $this->data=$json;
39        }catch(Exception $e){
40            return false;
41        }
42        return true;
43    }
44    function process(){
45        $continue = false;
46        if ($this->readFile()){
47            $continue = true;
48        }else{
49            $this->newFile();
50            if ($this->readFile()){
51                $continue=true;
52            }
53        }
54        if (! $continue){
55            return false;
56        }
57        if (! $this->parseJson()){
58            return false;
59        }
60        return true;
61    }
62    function get($key){
63        if (! $this->process()){
64            return false;
65        }
66        try{
67            $data=$this->data;
68            if ($data == null or ! is_array($data) or ! array_key_exists($key,$data))
69                return false;
70            $data=$data[$key];
71            if (time() - (int)$data['timestamp'] > $this->cache_timeout){
72                return false; //'invalid';
73            }else{
74                return $data['value'];
75            }
76        }catch(Exception $e){
77            return false;
78        }
79    }
80    function store($obj,$key){
81        if (! $this->process()){
82            return false;
83        }
84        try{
85            $this->data[$key]=['timestamp'=>time(),'value'=>$obj];
86            return $this->newFile($this->data);
87        }catch(Exception $e){
88            return false;
89        }
90    }
91}
92
93class DB{
94
95     private $dbhost;
96     private $dbname;
97     private $dbuser;
98     private $dbpass;
99     private $ka_file='/var/run/analyticsd.keepalive';
100     private $alias;
101     public $dbconn;
102
103     function DB(){
104         require_once('config.php');
105         global $dbhost,$dbname,$dbpass,$dbuser,$distros;
106
107         $this->dbhost=$dbhost;
108         $this->dbname=$dbname;
109         $this->dbpass=$dbpass;
110         $this->dbuser=$dbuser;
111         $this->alias=array();
112         $this->info_distro=json_decode($distros,true);
113         if ($this->info_distro == NULL){
114             die('Error: Wrong json in Config.php');
115         }
116         $this->init_dates();
117         $this->times=0;
118     }
119     function init_dates(){
120         $this->dates=array();
121         $this->dates['today']=date("Y-m-d");
122         $this->dates['first_current']=date("Y-m-").'01';
123         $this->dates['last_old']=date("Y-m-d",strtotime($this->dates['first_current']." -1 days"));
124         $this->dates['first_old']=date("Y-m-",strtotime($this->dates['today']. "-1 months")).'01';
125         $this->dates['last_very_old']=date("Y-m-d",strtotime($this->dates['first_old']." -1 days"));
126         $this->dates['first_very_old']=date("Y-m-",strtotime($this->dates['first_old']." -1 days")).'01';
127         $this->dates['date_current']="(date between '".$this->dates['first_current']."' and '".$this->dates['today']."')";
128         $this->dates['date_old']="(date between '".$this->dates['first_old']."' and '".$this->dates['last_old']."')";
129         $this->dates['date_very_old']="(date between '".$this->dates['first_very_old']."' and '".$this->dates['last_very_old']."')";
130         $this->dates['date_range_last_three_months']="(date between '".$this->dates['first_very_old']."' and '".$this->dates['today']."')";
131     }
132     function connect(){
133        $this->dbconn=new mysqli($this->dbhost, $this->dbuser , $this->dbpass, $this->dbname);
134        if ($this->dbconn->connect_error) {
135            die('Connect Error:'. $this->dbconn->connect_error);
136        }
137     }
138     function disconnect(){
139         $this->dbconn->close();
140     }
141     function init_trans(){
142         $this->dbconn->autocommit(FALSE);
143         $this->dbconn->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
144     }
145
146        function send_data($user,$version,$sabor,$apps,$date=''){
147            if ($date == ''){
148                $sql="INSERT INTO tmp_clients(user,version,sabor,status) values ('$user','$version','$sabor',0)";
149            }else{
150                $sql="INSERT INTO tmp_clients(user,version,sabor,status,date) values ('$user','$version','$sabor',0,'$date')";
151            }
152            $retry=1;
153            $done=false;
154            $cli_id=false;
155            while (! $done and $retry < 4){
156                $res=$this->dbconn->query($sql);
157                if ($res){
158                    $cli_id=$this->dbconn->insert_id; 
159                    $done=true;
160                }else{
161                    $retry+=1;
162                    sleep($retry);
163                }
164            }
165            if ($retry == 4 or $cli_id == false)
166                    throw new Exception('Error sending client data: '.$this->dbconn->error);
167            $err_apps=false;
168            $err_exception=false;
169            if (count($apps) != 0){
170                if ($date == ''){
171                    $sql="insert into tmp_packages(client,app,value) values";
172                }else{
173                    $sql="insert into tmp_packages(client,app,value,date) values";
174                }
175                $values=array();
176                foreach ($apps as $app => $value){
177                    if (trim($app) == '' or trim($value) == ''){
178                        $err_apps=true;
179                        $err_exception=new Exception('Wrong application values');
180                        continue;
181                    }
182                    if ($date == ''){
183                        $values[]="($cli_id,'$app',$value)";
184                    }else{
185                        $values[]="($cli_id,'$app',$value,'$date')";
186                    }
187                }
188                if (count($values) > 0){
189                    $sql.=implode(',',$values);
190                    $done=false;
191                    $retry=1;
192                    while (! $done and $retry < 4){
193                        $res=$this->dbconn->query($sql);
194                        if ($res){
195                            $done=true;
196                        }else{
197                            $retry += 1;
198                            sleep($retry);
199                        }
200                    }
201                    if ($retry == 4 or ! $done){
202                        $err_apps=true;
203                        $err_exception=new Exception('Error sending client app data: '.$this->dbconn->error.' QUERY='.$sql);
204                    }
205                }
206            }
207            //End operations
208            $sql = "Update tmp_clients set status=1 where id = $cli_id and status=0";
209            $retry=1;
210            $done=false;
211            while (! $done and $retry < 4){
212                $res=$this->dbconn->query($sql);
213                if ($res){
214                    $done=true;
215                }else{
216                    $retry+=1;
217                    sleep($retry);
218                }
219            }
220            if ($retry == 4 or $cli_id == false){
221                throw new Exception('Error commiting client data: '.$this->dbconn->error);
222            }
223            if ($err_apps){
224                throw $err_exception;
225            }
226        }
227
228     private function load_alias(){
229         $sql="SELECT name,alias from Alias";
230         $result=$this->dbconn->query($sql);
231         while($row=$result->fetch_array(MYSQLI_ASSOC)){
232             $this->alias[$row['name']]=$row['alias'];
233         }
234     }
235        function get_system_data(){
236            $sql="select * from Config";
237            $result=$this->dbconn->query($sql);
238            if ($result){
239                $tmp=[];
240                foreach ($result->fetch_all(MYSQLI_ASSOC) as $value){
241                    $tmp[$value['name']]=$value['value'];
242                }
243                return json_encode($tmp);
244            }
245        }
246     function get_extended_data($app){
247         $today=date("Y-m-d");
248         $min_date=date("Y-m",strtotime($today." -1 year")).'-01';
249         $this->times=0;
250         // CLIENTS DISTRIBUTION PER RELEASE/FLAVOUR
251         $sql="select year(date) as year,month(date) as month,Releases_name,Flavours_name,count(*) as num_hosts from (select distinct Client_uid,date,Releases_name,Flavours_name from Client_Versions where date >= '$min_date' )t group by year,month,Releases_name,Flavours_name order by year Desc,month desc,Releases_name asc,num_hosts desc";
252
253            $cache = new Cache;
254            $cache_key='extended_1';
255            $data = $cache->get($cache_key);
256            $stime=microtime(true);
257            if ($data != false){
258                $clients_month=$data;
259            }else{
260             $result=$this->dbconn->query($sql);
261                if ($result){
262                 $this->times+=microtime(true)-$stime;
263                 $clients_month=[];
264                 $tmp=[];
265                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
266                     $date=$row['year'].'_'.$row['month'];
267                     $tmp[$date][$row['Releases_name']][$row['Flavours_name']]=intval($row['num_hosts']);
268                 }
269                 foreach ($tmp as $date){
270                     $clients_month[]=$date;
271                 }
272                    $cache->store($clients_month,$cache_key);
273             }else{
274                 $clients_month=$this->dbconn->error;
275             }
276            }
277
278         
279         // CLIENT UPDATES
280         $sql="select year,month,count(*) as nclients,sum(cnt)-count(*) as nclients_updated from (select Client_uid,count(Client_uid) as cnt,year(date) as year,month(date) as month from Client_Versions where date >= '$min_date' GROUP by Client_uid,year,month having count(Client_uid) >= 1 ) t group by year,month order by year desc,month desc";
281         $sql="select year,month,count(*) as nclients,sum(cnt)-count(*) as nclients_updated,Releases_name as rel,Flavours_name as fla from (select Client_uid,count(Client_uid) as cnt,year(date) as year,month(date) as month,Releases_name,Flavours_name from Client_Versions where date >= '$min_date' GROUP by Client_uid,year,month,Releases_name,Flavours_name having count(Client_uid) >= 1 ) t group by year,month,Releases_name,Flavours_name order by year desc,month desc";
282
283            $cache_key='extended_2';
284            $data = $cache->get($cache_key);
285            $stime=microtime(true);
286            if ($data != false){
287                $num_updates_month=$data;
288            }else{
289                $result=$this->dbconn->query($sql);
290
291             if ($result){
292                 $this->times+=microtime(true)-$stime;
293                 $i=0;
294             $tmp=[];
295                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
296                     $date=$row['year'].'_'.$row['month'];
297                 $tmp[$date][$row['rel']][$row['fla']]=intval($row['nclients_updated']);
298                 }
299             foreach($tmp as $date){
300                 $num_updates_month[]=$date;
301             }
302                    $cache->store($num_updates_month,$cache_key);
303             }else{
304                 $num_updates_month=$this->dbconn->error;
305             }
306            }
307
308
309         // CLIENT CHANGE RELEASE
310         $sql="select year,month,count(*) as upgrades_en_mes from (select year,month,Client_uid as cliente_upgradeado from (select Client_uid,year(date) as year,month(date) as month from Client_Versions where date >= '$min_date' GROUP by Client_uid,Releases_name,year,month)t group by month,year,Client_uid having(count(*))>1)t group by year,month order by year desc,month desc limit 12";
311
312
313            $cache_key='extended_3';
314            $data = $cache->get($cache_key);
315            $stime=microtime(true);
316            if ($data != false){
317                $change_releases=$data;
318            }else{
319                $result=$this->dbconn->query($sql);
320
321                if ($result){
322                 $this->times+=microtime(true)-$stime;
323                 $change_releases=[0,0,0,0,0,0,0,0,0,0,0,0];
324                 $i=0;
325                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
326                     $change_releases[$i++]=intval($row['upgrades_en_mes']);
327                 }
328                    $cache->store($change_releases,$cache_key);
329             }else{
330                 $change_releases=$this->dbconn->error;
331             }
332            }
333
334         
335         // CLIENT CHANGE FLAVOUR
336         $sql="select year,month,count(*) as cambio_sabor_en_mes from (select year,month,Client_uid as cliente_upgradeado from (select Client_uid,year(date) as year,month(date) as month from Client_Versions where date >= '$min_date' GROUP by Client_uid,Flavours_name,year,month)t group by month,year,Client_uid having(count(*))>1)t group by year,month order by year desc,month desc limit 12";
337
338            $cache_key='extended_4';
339            $data = $cache->get($cache_key);
340            $stime=microtime(true);
341            if ($data != false){
342                $change_flavour=$data;
343            }else{
344                $result=$this->dbconn->query($sql);
345
346             if ($result){
347                 $this->times+=microtime(true)-$stime;
348                 $i=0;
349                 $change_flavour=[0,0,0,0,0,0,0,0,0,0,0,0];
350                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
351                     $change_flavour[$i++]=intval($row['cambio_sabor_en_mes']);
352                 }
353                    $cache->store($change_flavour,$cache_key);
354             }else{
355                 $change_flavour=$this->dbconn->error;
356             }
357            }
358         
359         //sanitize input
360         if ($app != NULL){
361             $app=preg_grep('/^[a-zA-Z0-9\-_]+$/',array($app));
362             if ($app != NULL and isset($app[0])){
363                 $app=$this->dbconn->real_escape_string($app[0]);
364                 $stats['apps']=[];
365                 $stats['apps']['app']=$app;
366                 $sql="select year(date) as year,month(date) as month,string,Releases_name as rel,Flavours_name as fla,sum(count) as count from RecvPackages where string='$app' and date >= '$min_date' group by year,month,Releases_name,Flavours_name order by year desc,month desc,sum(count) desc";
367
368                    $cache_key="extended_app_$app";
369                    $data = $cache->get($cache_key);
370                    $stime=microtime(true);
371                    if ($data != false){
372                        $app_use=$data;
373                        $stats['apps']['app_use']=$app_use;
374                    }else{
375                        $result=$this->dbconn->query($sql);
376                        if ($result){
377                         $this->times+=microtime(true)-$stime;
378                         $tmp=[];
379                         while($row=$result->fetch_array(MYSQLI_ASSOC)){
380                             $date=$row['year'].'_'.$row['month'];
381                         $tmp[$date][$row['rel']][$row['fla']]=intval($row['count']);
382                         }
383                     foreach($tmp as $date){
384                         $app_use[]=$date;
385                     }
386                            $cache->store($app_use,$cache_key);
387                         $stats['apps']['app_use']=$app_use;
388                     }else{
389                         $app_use=$this->dbconn->error;
390                     }
391                    }
392
393             }
394         }
395         // FINALIZATION & WRITE STRUCTURE
396         if (isset($clients_month)){
397             $stats['clients']['clients_per_month']=$clients_month;
398         }
399         if (isset($num_updates_month)){
400             $stats['clients']['freq_updates_per_month']=$num_updates_month;
401         }
402         if (isset($change_releases)){
403             $stats['clients']['change_releases']=$change_releases;
404         }
405         if (isset($change_flavour)){
406             $stats['clients']['change_flavours']=$change_flavour;
407         }
408         $stats['debug_query_time']=strval(number_format($this->times,5));
409         if (file_exists($this->ka_file)){
410             $stats['debug_keep_alive']=date('Y-m-d H:i',file_get_contents($this->ka_file));
411         }
412         return json_encode($stats);
413     }
414
415     function get_historic_data(){
416         $this->load_alias();
417         $obj=[];
418         $this->times=0;
419         foreach ($this->info_distro['distros'] as $distro){
420             $dname=$distro['name'];
421             $dlike=$distro['like'];
422             $obj[$dname]=array();
423             foreach ($distro['sabor'] as $sabor){
424                 $sname=$sabor['name'];
425                 $slike=$sabor['like'];
426                 $obj[$dname][$sname][]=$this->get_chart($dlike,$slike,'current');
427                 $obj[$dname][$sname][]=$this->get_chart($dlike,$slike,'old');
428                 $obj[$dname][$sname][]=$this->get_chart($dlike,$slike,'very_old');
429             }
430         }
431         $obj['debug_query_time']=strval(number_format($this->times,3));
432         
433         if (file_exists($this->ka_file)){
434             $obj['debug_keep_alive']=date('Y-m-d H:i',file_get_contents($this->ka_file));
435         }
436         return json_encode($obj);
437     }
438    function get_chart($version='',$sabor='',$type='current'){
439        $cache_key="$version"."_"."$sabor"."_"."$type";
440        if ($version != ''){
441            $version = " and Releases_name = '$version' ";
442        }
443        if ($sabor != ''){
444            $sabor = " and Flavours_name = '$sabor' ";
445        }
446        $order=" order by count desc limit 10 ";
447        $group=" group by app ";
448
449        $where=$this->dates['date_'.$type]." $version $sabor ";
450        $where_clients=$this->dates['date_'.$type]." $version $sabor ";
451
452        $sql="SELECT string as app,sum(count) as count from RecvPackages where $where $group $order";
453        $sql_clients = "select count(distinct Client_uid) as count from Client_Versions where $where_clients $order";
454
455        $cache = new Cache;
456        $data = $cache->get($cache_key);
457        if ($data != false){
458            return $data;
459        }else{
460            $data=array($this->get_result_from_sql($sql),$this->get_clients_from_sql($sql_clients));
461            $cache->store($data,$cache_key);
462            return $data;
463        }
464     }
465     function get_clients_from_sql($sql){
466             $stime=microtime(true);
467         if ($result=$this->dbconn->query($sql)){
468                 $etime=microtime(true);
469                 $this->times+=($etime-$stime);
470             while($row=$result->fetch_array(MYSQLI_ASSOC)){
471                 if (isset($row['count'])){
472                     return array('nclients'=>$row['count']);
473                 }
474             }
475             return array('nclients'=>'not available');
476         }else{
477             return array('nclients'=>$this->dbconn->error);
478         }
479
480     }
481     function get_result_from_sql($sql){
482             $stime=microtime(true);
483         if ($result=$this->dbconn->query($sql)){
484                 $etime=microtime(true);
485                 $this->times+=($etime-$stime);
486             $obj2=[];
487             $nobj=0;
488             while($row=$result->fetch_array(MYSQLI_ASSOC)){
489                 if (array_key_exists($row['app'],$this->alias)){
490                     if (! empty($this->alias[$row['app']])){
491                         if ($nobj < 10)
492                             $obj2[$this->alias[$row['app']]]=$row['count'];
493                         $nobj++;
494                     }
495                 }else{
496                     if ($nobj < 10)
497                         $obj2[$row['app']]=$row['count'];
498                     $nobj++;
499                 }
500             }
501             return $obj2;
502         }else{
503             return $this->dbconn->error;
504         }
505     }
506
507} 
508
509?>
Note: See TracBrowser for help on using the repository browser.