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

Last change on this file since 6819 was 6819, 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
Support to provide blacklist
Support to platform data

File size: 20.6 KB
RevLine 
[350]1<?php
[6767]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
[350]93class DB{
94
[6767]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;
[350]102
[6767]103     function DB(){
104         require_once('config.php');
105         global $dbhost,$dbname,$dbpass,$dbuser,$distros;
[747]106
[6767]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     }
[5560]145
[6819]146    function send_data($user,$version,$sabor,$apps,$specs,$date=''){
147        $spec_sql_names = '';
148        $spec_sql_values = '';
149        if ($specs != false){
150            try{
151                $arch = $specs['arch'];
152                $mem = $specs['mem'];
153                if (is_numeric($mem)){
154                    $mem=(int)$mem;
155                }
156                $vga = $specs['vga'];
157                $cpu = $specs['cpu']['model'];
158                $ncpu = $specs['cpu']['ncpus'];
159                if (is_numeric($ncpu)){
160                    $ncpu=(int)$ncpu;
161                }
162                $spec_sql_names = ',arch,mem,vga,cpu,ncpu';
163                $spec_sql_values = ",'$arch',$mem,'$vga','$cpu',$ncpu";
164            }catch(Exception $e){
165                $spec_sql_names = '';
166                $spec_sql_values = '';
167            }
168        }
169        if ($date == ''){
170            $sql="INSERT INTO tmp_clients(user,version,sabor,status $spec_sql_names) values ('$user','$version','$sabor',0 $spec_sql_values)";
171        }else{
172            $sql="INSERT INTO tmp_clients(user,version,sabor,status,date $spec_sql_names) values ('$user','$version','$sabor',0,'$date' $spec_sql_values)";
173        }
174        $retry=1;
175        $done=false;
176        $cli_id=false;
177        while (! $done and $retry < 4){
178            $res=$this->dbconn->query($sql);
179            if ($res){
180                $cli_id=$this->dbconn->insert_id; 
181                $done=true;
182            }else{
183                $retry+=1;
184                sleep($retry);
185            }
186        }
187        if ($retry == 4 or $cli_id == false)
188            throw new Exception('Error sending client data: '.$this->dbconn->error);
189
190        $err_apps=false;
191        $err_exception=false;
192        if (count($apps) != 0){
[5560]193            if ($date == ''){
[6819]194                $sql="insert into tmp_packages(client,app,value) values";
[5560]195            }else{
[6819]196                $sql="insert into tmp_packages(client,app,value,date) values";
[5560]197            }
[6819]198            $values=array();
199            // Prevent DoS attack
200            $i = 1000;
201            foreach ($apps as $app => $value){
202                // Max 1000 apps
203                if ( $i > 0 ){
204                    $i = $i - 1;
[5560]205                }else{
[6819]206                    throw new Exception('*** DoS detected, aborting more processing on this request ***');
207                } 
208
209                if (trim($app) == '' or trim($value) == ''){
210                    $err_apps=true;
211                    $err_exception=new Exception('Wrong application values');
212                    continue;
[5560]213                }
214                if ($date == ''){
[6819]215                    $values[]="($cli_id,'$app',$value)";
[5560]216                }else{
[6819]217                    $values[]="($cli_id,'$app',$value,'$date')";
[5560]218                }
[6819]219            }
220            if (count($values) > 0){
221                $sql.=implode(',',$values);
222                $done=false;
223                $retry=1;
224                while (! $done and $retry < 4){
225                    $res=$this->dbconn->query($sql);
226                    if ($res){
227                        $done=true;
[5560]228                    }else{
[6819]229                        $retry += 1;
230                        sleep($retry);
[5560]231                    }
232                }
[6819]233                if ($retry == 4 or ! $done){
234                    $err_apps=true;
235                    $err_exception=new Exception('Error sending client app data: '.$this->dbconn->error.' QUERY='.$sql);
[5560]236                }
237            }
[6819]238        }
239        //End operations
240        $sql = "Update tmp_clients set status=1 where id = $cli_id and status=0";
241        $retry=1;
242        $done=false;
243        while (! $done and $retry < 4){
244            $res=$this->dbconn->query($sql);
245            if ($res){
246                $done=true;
247            }else{
248                $retry+=1;
249                sleep($retry);
[5560]250            }
251        }
[6819]252        if ($retry == 4 or $cli_id == false){
253            throw new Exception('Error commiting client data: '.$this->dbconn->error);
254        }
255        if ($err_apps){
256            throw $err_exception;
257        }
258    }
[5560]259
[6767]260     private function load_alias(){
261         $sql="SELECT name,alias from Alias";
262         $result=$this->dbconn->query($sql);
263         while($row=$result->fetch_array(MYSQLI_ASSOC)){
264             $this->alias[$row['name']]=$row['alias'];
265         }
266     }
267        function get_system_data(){
268            $sql="select * from Config";
269            $result=$this->dbconn->query($sql);
270            if ($result){
271                $tmp=[];
272                foreach ($result->fetch_all(MYSQLI_ASSOC) as $value){
273                    $tmp[$value['name']]=$value['value'];
274                }
275                return json_encode($tmp);
276            }
277        }
278     function get_extended_data($app){
279         $today=date("Y-m-d");
280         $min_date=date("Y-m",strtotime($today." -1 year")).'-01';
281         $this->times=0;
282         // CLIENTS DISTRIBUTION PER RELEASE/FLAVOUR
283         $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";
[4854]284
[6767]285            $cache = new Cache;
286            $cache_key='extended_1';
287            $data = $cache->get($cache_key);
288            $stime=microtime(true);
289            if ($data != false){
290                $clients_month=$data;
291            }else{
292             $result=$this->dbconn->query($sql);
293                if ($result){
294                 $this->times+=microtime(true)-$stime;
295                 $clients_month=[];
296                 $tmp=[];
297                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
298                     $date=$row['year'].'_'.$row['month'];
299                     $tmp[$date][$row['Releases_name']][$row['Flavours_name']]=intval($row['num_hosts']);
300                 }
301                 foreach ($tmp as $date){
302                     $clients_month[]=$date;
303                 }
304                    $cache->store($clients_month,$cache_key);
305             }else{
306                 $clients_month=$this->dbconn->error;
307             }
308            }
[938]309
[6767]310         
311         // CLIENT UPDATES
312         $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";
313         $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";
[4854]314
[6767]315            $cache_key='extended_2';
316            $data = $cache->get($cache_key);
317            $stime=microtime(true);
318            if ($data != false){
319                $num_updates_month=$data;
320            }else{
321                $result=$this->dbconn->query($sql);
[5560]322
[6767]323             if ($result){
324                 $this->times+=microtime(true)-$stime;
325                 $i=0;
326             $tmp=[];
327                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
328                     $date=$row['year'].'_'.$row['month'];
329                 $tmp[$date][$row['rel']][$row['fla']]=intval($row['nclients_updated']);
330                 }
331             foreach($tmp as $date){
332                 $num_updates_month[]=$date;
333             }
334                    $cache->store($num_updates_month,$cache_key);
335             }else{
336                 $num_updates_month=$this->dbconn->error;
337             }
338            }
339
340
341         // CLIENT CHANGE RELEASE
342         $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";
343
344
345            $cache_key='extended_3';
346            $data = $cache->get($cache_key);
347            $stime=microtime(true);
348            if ($data != false){
349                $change_releases=$data;
350            }else{
351                $result=$this->dbconn->query($sql);
352
353                if ($result){
354                 $this->times+=microtime(true)-$stime;
355                 $change_releases=[0,0,0,0,0,0,0,0,0,0,0,0];
356                 $i=0;
357                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
358                     $change_releases[$i++]=intval($row['upgrades_en_mes']);
359                 }
360                    $cache->store($change_releases,$cache_key);
361             }else{
362                 $change_releases=$this->dbconn->error;
363             }
364            }
365
366         
367         // CLIENT CHANGE FLAVOUR
368         $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";
369
370            $cache_key='extended_4';
371            $data = $cache->get($cache_key);
372            $stime=microtime(true);
373            if ($data != false){
374                $change_flavour=$data;
375            }else{
376                $result=$this->dbconn->query($sql);
377
378             if ($result){
379                 $this->times+=microtime(true)-$stime;
380                 $i=0;
381                 $change_flavour=[0,0,0,0,0,0,0,0,0,0,0,0];
382                 while($row=$result->fetch_array(MYSQLI_ASSOC)){
383                     $change_flavour[$i++]=intval($row['cambio_sabor_en_mes']);
384                 }
385                    $cache->store($change_flavour,$cache_key);
386             }else{
387                 $change_flavour=$this->dbconn->error;
388             }
389            }
390         
391         //sanitize input
392         if ($app != NULL){
393             $app=preg_grep('/^[a-zA-Z0-9\-_]+$/',array($app));
394             if ($app != NULL and isset($app[0])){
395                 $app=$this->dbconn->real_escape_string($app[0]);
396                 $stats['apps']=[];
397                 $stats['apps']['app']=$app;
398                 $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";
399
400                    $cache_key="extended_app_$app";
401                    $data = $cache->get($cache_key);
402                    $stime=microtime(true);
403                    if ($data != false){
404                        $app_use=$data;
405                        $stats['apps']['app_use']=$app_use;
406                    }else{
407                        $result=$this->dbconn->query($sql);
408                        if ($result){
409                         $this->times+=microtime(true)-$stime;
410                         $tmp=[];
411                         while($row=$result->fetch_array(MYSQLI_ASSOC)){
412                             $date=$row['year'].'_'.$row['month'];
413                         $tmp[$date][$row['rel']][$row['fla']]=intval($row['count']);
414                         }
415                     foreach($tmp as $date){
416                         $app_use[]=$date;
417                     }
418                            $cache->store($app_use,$cache_key);
419                         $stats['apps']['app_use']=$app_use;
420                     }else{
421                         $app_use=$this->dbconn->error;
422                     }
423                    }
424
425             }
426         }
427         // FINALIZATION & WRITE STRUCTURE
428         if (isset($clients_month)){
429             $stats['clients']['clients_per_month']=$clients_month;
430         }
431         if (isset($num_updates_month)){
432             $stats['clients']['freq_updates_per_month']=$num_updates_month;
433         }
434         if (isset($change_releases)){
435             $stats['clients']['change_releases']=$change_releases;
436         }
437         if (isset($change_flavour)){
438             $stats['clients']['change_flavours']=$change_flavour;
439         }
440         $stats['debug_query_time']=strval(number_format($this->times,5));
441         if (file_exists($this->ka_file)){
442             $stats['debug_keep_alive']=date('Y-m-d H:i',file_get_contents($this->ka_file));
443         }
444         return json_encode($stats);
445     }
446
447     function get_historic_data(){
448         $this->load_alias();
449         $obj=[];
450         $this->times=0;
451         foreach ($this->info_distro['distros'] as $distro){
452             $dname=$distro['name'];
453             $dlike=$distro['like'];
454             $obj[$dname]=array();
455             foreach ($distro['sabor'] as $sabor){
456                 $sname=$sabor['name'];
457                 $slike=$sabor['like'];
458                 $obj[$dname][$sname][]=$this->get_chart($dlike,$slike,'current');
459                 $obj[$dname][$sname][]=$this->get_chart($dlike,$slike,'old');
460                 $obj[$dname][$sname][]=$this->get_chart($dlike,$slike,'very_old');
461             }
462         }
463         $obj['debug_query_time']=strval(number_format($this->times,3));
464         
465         if (file_exists($this->ka_file)){
466             $obj['debug_keep_alive']=date('Y-m-d H:i',file_get_contents($this->ka_file));
467         }
468         return json_encode($obj);
469     }
470    function get_chart($version='',$sabor='',$type='current'){
471        $cache_key="$version"."_"."$sabor"."_"."$type";
472        if ($version != ''){
473            $version = " and Releases_name = '$version' ";
474        }
475        if ($sabor != ''){
476            $sabor = " and Flavours_name = '$sabor' ";
477        }
478        $order=" order by count desc limit 10 ";
479        $group=" group by app ";
480
481        $where=$this->dates['date_'.$type]." $version $sabor ";
482        $where_clients=$this->dates['date_'.$type]." $version $sabor ";
483
484        $sql="SELECT string as app,sum(count) as count from RecvPackages where $where $group $order";
485        $sql_clients = "select count(distinct Client_uid) as count from Client_Versions where $where_clients $order";
486
487        $cache = new Cache;
488        $data = $cache->get($cache_key);
489        if ($data != false){
490            return $data;
491        }else{
492            $data=array($this->get_result_from_sql($sql),$this->get_clients_from_sql($sql_clients));
493            $cache->store($data,$cache_key);
494            return $data;
495        }
496     }
497     function get_clients_from_sql($sql){
498             $stime=microtime(true);
499         if ($result=$this->dbconn->query($sql)){
500                 $etime=microtime(true);
501                 $this->times+=($etime-$stime);
502             while($row=$result->fetch_array(MYSQLI_ASSOC)){
503                 if (isset($row['count'])){
504                     return array('nclients'=>$row['count']);
505                 }
506             }
507             return array('nclients'=>'not available');
508         }else{
509             return array('nclients'=>$this->dbconn->error);
510         }
511
512     }
513     function get_result_from_sql($sql){
514             $stime=microtime(true);
515         if ($result=$this->dbconn->query($sql)){
516                 $etime=microtime(true);
517                 $this->times+=($etime-$stime);
518             $obj2=[];
519             $nobj=0;
520             while($row=$result->fetch_array(MYSQLI_ASSOC)){
521                 if (array_key_exists($row['app'],$this->alias)){
522                     if (! empty($this->alias[$row['app']])){
523                         if ($nobj < 10)
524                             $obj2[$this->alias[$row['app']]]=$row['count'];
525                         $nobj++;
526                     }
527                 }else{
528                     if ($nobj < 10)
529                         $obj2[$row['app']]=$row['count'];
530                     $nobj++;
531                 }
532             }
533             return $obj2;
534         }else{
535             return $this->dbconn->error;
536         }
537     }
538
[350]539} 
540
541?>
Note: See TracBrowser for help on using the repository browser.