source: lliurex-analytics-server/trunk/fuentes/testing_utils/test_analytics.py @ 5560

Last change on this file since 5560 was 5560, checked in by mabarracus, 3 years ago

Complete code rewrite
New database model
Improved performance & optimization
Extended information about clients
Fixed older bugs
New testing framework
Fix postinst exit code

  • Property svn:executable set to *
File size: 18.9 KB
Line 
1#!/usr/bin/python
2import _mysql
3import urllib2
4import requests
5import sys
6from collections import deque
7import random
8from multiprocessing.dummy import Pool as ThreadPool
9from multiprocessing.dummy import Lock
10import json
11import time
12import operator
13import threading
14import signal
15from datetime import datetime
16from dateutil.relativedelta import relativedelta
17
18IP=''
19HOST='http://aplicaciones.lliurex.net/analytics/notify'
20HEADERS={'user-agent':'lliurex-statistics-agent'}
21TIMEOUT=10              # TIMEOUT SENDING REQUESTS
22
23RELEASES=['16','15']
24MAX_RELNUMBER=5         # MAX RELEASE VARIATION
25REAL_FLAVOURS=True      # TRUE FLAVOURS (release 15)
26DATE_ROUNDS=15           # DIFERENTS MONTHS SENDED
27NCLIENTS=3000           # NUMBER DIFFERENT CLIENTS
28MAX_THREADS=5           # PARALLEL CLIENTS SENDING DATA (> 5 == no effect)
29CACHE_CONNECTIONS=1     # python requests cache hosts
30NAPPS=5000                # MAX NUMBER DIFFERENT APPS
31CLIENT_MAX_APPS=10      # MAX NUMBER APPS SENDING
32CLIENT_UPDATE_FREQ=5    # PERCENT OF CLIENTS THAT UPDATE INTRA ROUND
33LOOPS=5                # LOOPS INTRA DATE LOOP
34SLEEP_THREAD_TIME=0     # SLEEP THREAD AFTER SENDING
35STATS_ON=1              # req/s stats
36
37MAX_RANDOM_CLIENTS=50000
38DEBUG=0
39SHOW_BY_APP=True
40PAUSE_SHOW_STATS=False
41PAUSE_WITH_DATE_ROUNDS=False
42# STAT COUNTERS
43NPET=0
44SUCCESS=0
45FAILED=0
46REDO=0
47UPDATED_TOTAL=0
48UPDATED=0
49RANDOM=True
50
51exit_threads=0
52FLAVOURS={}
53if REAL_FLAVOURS:
54    FLAVOURS['16']=[
55                {'name':'edu,server','real':'server'},
56                {'name':'server','real':'server'},
57                {'name':'edu','real':'other'},
58                {'name':'edu,music','real':'other'},
59                {'name':'client','real':'client'},
60                {'name':'client','real':'client'},
61                {'name':'desktop,edu','real':'desktop'},
62                {'name':'client,edu','real':'client'},
63                {'name':'client,edu,infantil','real':'client'},
64                {'name':'desktop,edu','real':'desktop'},
65                {'name':'desktop,edu,infantil','real':'desktop'},
66                {'name':'desktop,edu,infantil,music','real':'desktop'},
67                {'name':'desktop,edu,music','real':'desktop'},
68                {'name':'server','real':'server'},
69                {'name':'pime','real':'other'},
70                {'name':'server','real':'server'}]
71    FLAVOURS['15']=[
72                {'name':'cdd,class,client,desktop,edu,lliurex,ltsp,net,network-client-promo','real':'client'},
73                {'name':'cdd,lliurex','real':'other'},
74                {'name':'cdd,lliurex,network-client-promo','real':'client'},
75                {'name':'cdd,desktop,lliurex','real':'desktop'},
76                {'name':'cdd,desktop,lliurex,music','real':'desktop'},
77                {'name':'cdd,desktop,edu,lite-extended,lliurex','real':'desktop'},
78                {'name':'cdd,desktop,edu,infantil,lliurex','real':'desktop'},
79                {'name':'cdd,desktop,gva,lliurex','real':'desktop'},
80                {'name':'cdd,desktop,edu,enterprise,infantil,lliurex','real':'desktop'},
81                {'name':'cdd,class,desktop,edu,lliurex,ltsp,server','real':'server'},
82                {'name':'server','real':'server'},
83                {'name':'client','real':'client'},
84                {'name':'desktop','real':'desktop'},
85                {'name':'cdd,class,desktop,edu,live,lliurex,ltsp,server','real':'server'},
86                {'name':'server,cdd','real':'server'},
87                {'name':'client,cdd','real':'client'}]
88else:
89    FLAVOURS['16']=[
90                {'name':'server','real':'server'},
91                {'name':'client','real':'client'},
92                {'name':'desktop','real':'desktop'},
93                {'name':'other','real':'other'}]
94    FLAVOURS['15']=[
95                {'name':'server','real':'server'},
96                {'name':'client','real':'client'},
97                {'name':'desktop','real':'desktop'},
98                {'name':'other','real':'other'}]
99
100
101
102class DB():
103    def __init__(self):
104        self.conn=None
105
106    def init_db(self):
107        try:
108            self.conn = _mysql.connect(IP,USER,PASS,DBNAME)
109            print "Connected succesfully"
110
111        except _mysql.Error, e:
112            print "Error {}: {}".format(e.args[0],e.args[1])
113            sys.exit(1)
114
115    def close_db(self):
116        if self.conn:
117            self.conn.close()
118            print "Closed connection"
119
120if NCLIENTS <= MAX_RANDOM_CLIENTS:
121    MACS_USED=[]
122else:
123    MAC_LIST=deque()
124    a=0
125    b=0
126    c=0
127    for i in range(NCLIENTS):
128        c+=1
129        mac = "02:00:00:%02x:%02x:%02x" % (a,b,c)
130        if c == 255:
131            c=0
132            b+=1
133            sys.stderr.write('{}Generating consecutive mac: {:07d}'.format('\r'*100,i+1))
134        if b == 255:
135            b=0
136            c+=1
137        MAC_LIST.append(mac)
138    sys.stderr.write('{}Generating consecutive mac: {:07d}\n'.format('\r'*100,NCLIENTS))
139
140class CLIENT():
141    def __init__(self):
142        self.mac=self.gen_mac()
143        self.release=RELEASES[random.randint(0,4)%2] # more probabilities for the first listed release
144        self.release_number=self.gen_rel_number(self.release)
145        fla=FLAVOURS[self.release][random.randint(0,len(FLAVOURS[self.release])-1)]
146        self.flavour=fla['name']
147        self.real_flavour=fla['real']
148        self.updates=0
149        self.inc_date=0
150
151    def info(self):
152        print '{} {} {} {}'.format(self.mac,self.release,self.release_number,self.flavour)
153
154    def gen_mac(self):
155        global NCLIENTS,MAX_RANDOM_CLIENTS,MACS_USED
156
157        if NCLIENTS <= MAX_RANDOM_CLIENTS:
158            mac = "02:00:00:%02x:%02x:%02x" % (random.randint(0, 255),random.randint(0, 255),random.randint(0, 255))
159            while mac in MACS_USED:
160                mac = "02:00:00:%02x:%02x:%02x" % (random.randint(0, 255),random.randint(0, 255),random.randint(0, 255))
161            MACS_USED.append(mac)
162            return mac
163        else:
164            mac = MAC_LIST.popleft()
165            return mac
166
167    def gen_rel_number(self,release,dummy=False):
168        if release == '15':
169            if not dummy:
170                self.num=random.randint(100,100+MAX_RELNUMBER)
171            rnum='15.05.0.{}'.format(self.num)
172        else:
173            if not dummy:
174                self.year=random.randint(16,16+(MAX_RELNUMBER/12))
175                self.month=random.randint(1,1+(MAX_RELNUMBER%12))
176                self.day=1
177            rnum='16.{}{:02d}{:02d}'.format(self.year,self.month,self.day)
178        return rnum
179
180    def gen_apps(self):
181        global RANDOM,CLIENT_MAX_APPS,NAPPS
182        l={}
183        if RANDOM:
184            napps=random.randint(0,CLIENT_MAX_APPS)
185        else:
186            napps=CLIENT_MAX_APPS
187            start_app_at=random.randint(1,NAPPS-CLIENT_MAX_APPS)
188        for i in range(napps):
189            if RANDOM:
190                apprandname='app'+str(random.randint(1,NAPPS))
191                while apprandname in l:
192                    apprandname='app'+str(random.randint(1,NAPPS))
193                l[apprandname]=str(random.randint(1,10))
194            else:
195                apprandname='app'+str(start_app_at+i)
196                l[apprandname]=10
197        return (l,napps)
198
199    def update(self):
200        if self.release == '15':
201            self.num = self.num +1
202        else:
203            self.month = self.month +1
204        self.updates = self.updates +1
205        self.release_number=self.gen_rel_number(self.release,True)
206       
207    def get_data(self):
208        tmp={}
209        tmp['sab']=self.flavour
210        tmp['vers']=self.release_number
211        apps,napps=self.gen_apps()
212        tmp['stats']=json.dumps(apps)
213        tmp['uid']=self.mac
214        if self.inc_date > 0:
215            d=datetime.today()+relativedelta(months=0-self.inc_date)
216            tmp['date']=d.strftime('%Y-%m-%d %H:%M:%S')
217        return ({'stats':json.dumps(tmp)},napps,apps)
218
219class TEST():
220    def __init__(self):
221        self.client_list = deque()
222        self.db = DB()
223        self.stats={}
224        self.pool=None
225        self.lock=Lock()
226        signal.signal(signal.SIGTERM,self.term_test)
227        signal.signal(signal.SIGINT,self.term_test)
228
229    def term_test(self,*args,**kwargs):
230        global STATS_ON
231        global exit_threads
232        exit_threads=1
233        STATS_ON=0
234        print "Exitting.."
235        sys.exit(1)
236
237    def prepare(self):
238        print 'Preparing clients... '
239        pad=len(str(NCLIENTS))
240        clean='\r\r\r\r\r\r\r\r\r\r';
241        for i in range(NCLIENTS):
242            sys.stderr.write(clean+str(i+1).zfill(pad))
243            cli=CLIENT()
244            #cli.info()
245            self.client_list.append(cli)
246        sys.stderr.write(clean+str(NCLIENTS)+' generated!\n')
247        self.do_stats()
248        self.print_stats()
249
250    def prepare_threads(self):
251        global MAX_THREADS
252        if (NCLIENTS < MAX_THREADS):
253            MAX_THREADS=NCLIENTS
254            print("NEW NTHREADS {}".format(MAX_THREADS))
255        self.pool = ThreadPool(processes=MAX_THREADS)
256        sys.stderr.write('Created pool for {} threads, {} clients per thread\n'.format(MAX_THREADS,int(NCLIENTS/MAX_THREADS)))
257        self.sess = requests.Session()
258        adapter = requests.adapters.HTTPAdapter(pool_connections=CACHE_CONNECTIONS,pool_maxsize=MAX_THREADS+1,max_retries=3,pool_block=False)
259        self.sess.mount('http://',adapter)
260
261    def init_test(self):
262        global STATS_ON
263        self.prepare()
264        #self.db.init_db()
265        #self.db.close_db()
266        self.prepare_threads()
267        if STATS_ON and STATS_ON != 0:
268            STATS_ON=1
269            try:
270                threading.Thread(target=self.time_stats).start()
271            except:
272                pass
273        self.start()
274        STATS_ON=0
275
276    def do_stats(self):
277        for rel in RELEASES:
278            self.stats[rel]={}
279            self.stats[rel]['nclients']=0
280            self.stats[rel]['nclients_updated']=0
281            self.stats[rel]['flavours']={}
282
283        for cli in self.client_list:
284            self.stats[cli.release]['nclients']=self.stats[cli.release]['nclients']+1
285            self.stats[cli.release]['nclients_updated']=self.stats[cli.release]['nclients_updated']+cli.updates
286            if cli.real_flavour not in self.stats[cli.release]['flavours']:
287                self.stats[cli.release]['flavours'][cli.real_flavour]=1
288            else:
289                self.stats[cli.release]['flavours'][cli.real_flavour]=self.stats[cli.release]['flavours'][cli.real_flavour]+1
290
291    def print_stats(self):
292        print ''
293        for rel in RELEASES:
294            print 'RELEASE {}: {} clients ({} updated)'.format(rel,self.stats[rel]['nclients'],self.stats[rel]['nclients_updated'])
295            keys=self.stats[rel]['flavours'].keys()
296            keys.sort()
297            for key in keys:
298                print '\t{} : {}'.format(key,self.stats[rel]['flavours'][key])
299            print ''
300
301
302    def time_stats(self):
303        global STATS_ON,NPET,PAUSE_SHOW_STATS,SUCCESS,DATE_ROUNDS
304        global NCLIENTS,LOOPS
305        print "STATS_ON"
306        total=NCLIENTS*LOOPS
307        old_npet=[]*9
308        f=lambda x,y: x+y
309        while STATS_ON and STATS_ON != 0:
310            if not PAUSE_SHOW_STATS:
311                old_npet.append(NPET)
312                med_npet=reduce(f,old_npet)/len(old_npet)
313                old_npet=old_npet[1-len(old_npet):]
314                sys.stderr.write(' '*20+'\r'*20+'{} r/s ETA:{} secs'.format(int(med_npet),int((total-SUCCESS)/(med_npet+1))))
315                self.lock.acquire()
316                NPET=0
317                self.lock.release()
318            time.sleep(1)
319
320    def start(self):
321        global APPS_SENT,ALL_BY_APP,PAUSE_SHOW_STATS,PAUSE_WITH_DATE_ROUNDS
322        global STATS_ON,SHOW_BY_APP,NPET,SUCCESS,FAILED,REDO,UPDATED,UPDATED_TOTAL
323
324
325        #DATE_DATA=[]
326        ALL_BY_APP={}
327        for k in range(DATE_ROUNDS):
328          NPET=0
329          SUCCESS=0
330          FAILED=0
331          REDO=0
332          UPDATED=0
333          start_time=time.time()
334          if (k > 0):
335            for cli in self.client_list:
336                cli.inc_date+=1
337          TOTAL_SENT={}
338          ALL_SENT={}
339          for rel in ['15','16']:
340            TOTAL_SENT[rel]={}
341            ALL_SENT[rel]={}
342            for fla in ['desktop','server','client','other']:
343                TOTAL_SENT[rel][fla]=0
344                ALL_SENT[rel][fla]=0
345                ALL_SENT[rel][fla+'apps']={}
346
347          for i in range(LOOPS):
348              UPDATED_TOTAL=UPDATED
349              sys.stderr.write('\nRound {}/{} Date round {}/{} ...\n'.format(i+1,LOOPS,k+1,DATE_ROUNDS))
350              APPS_SENT={}
351              for rel in ['15','16']:
352                  APPS_SENT[rel]={}
353                  for fla in ['desktop','server','client','other']:
354                      APPS_SENT[rel][fla]=0
355                      APPS_SENT[rel][fla+'apps']={}
356
357              self.pool.map_async(self.thread_code,self.client_list).get(timeout=1000000)
358
359              for rel in ['15','16']:
360                  for fla in ['desktop','server','client','other']:
361                      if not STATS_ON and DEBUG and DEBUG > 1:
362                         sys.stderr.write('\t{}:{} ({} apps sent ((tmp_packages count)insert or update))\n'.format(rel,fla,APPS_SENT[rel][fla]))
363                      TOTAL_SENT[rel][fla]=TOTAL_SENT[rel][fla]+APPS_SENT[rel][fla]
364                      ALL_SENT[rel][fla]=ALL_SENT[rel][fla]+APPS_SENT[rel][fla]
365                      for app in APPS_SENT[rel][fla+'apps']:
366                          if app not in ALL_SENT[rel][fla+'apps']:
367                              ALL_SENT[rel][fla+'apps'][app]=APPS_SENT[rel][fla+'apps'][app]
368                          else:
369                              ALL_SENT[rel][fla+'apps'][app]+=APPS_SENT[rel][fla+'apps'][app]
370              if not STATS_ON:
371                sys.stderr.write('\n')
372
373          #STATS_ON=0
374          total=0
375          print "\n\nTOTAL APPS SENT AFTER DATE_ROUND {}/{}\n".format(k+1,DATE_ROUNDS)
376          other_all_sent={}
377          #mini_date_data={}
378          for rel in ['15','16']:
379              total_rel=0
380              #mini_date_data[rel]={}
381              for fla in ['desktop','server','client','other']:
382                  print "\tBY {} {} : {} sent (insert or update)(tmp_packages count)".format(rel,fla,ALL_SENT[rel][fla])
383                  total_rel=total_rel+ALL_SENT[rel][fla]
384                  if fla == 'other':
385                      for x in ALL_SENT[rel][fla+'apps']:
386                          if x in other_all_sent:
387                              other_all_sent[x]+=ALL_SENT[rel][fla+'apps'][x]
388                          else:
389                              other_all_sent[x]=ALL_SENT[rel][fla+'apps'][x]
390                  else:
391                      sorted_x=sorted(ALL_SENT[rel][fla+'apps'].items(),key=operator.itemgetter(1),reverse=True)
392                      #mini_date_data[rel][fla]=ALL_SENT[rel][fla+'apps'];
393                      for x in sorted_x[0:10]:
394                        print "\t\t{}:{}".format(x[0],x[1])
395              print "\tTOTAL RELEASE {} : {} sent (insert or update)(tmp_packages count)".format(rel,total_rel)
396              total=total+total_rel
397         
398          print "\tOTHER (combined 15,16):"
399          sorted_x=sorted(other_all_sent.items(),key=operator.itemgetter(1),reverse=True)
400          for x in sorted_x[0:10]:
401              print "\t\t{}:{}".format(x[0],x[1])
402          #mini_date_data['other']={'other':other_all_sent}
403          #DATE_DATA.append(mini_date_data)
404          print "TOTAL ALL RELEASES : {} sent (insert or updated)(tmp_packages count)".format(total)
405          if SHOW_BY_APP:
406                sorted_apps=sorted(ALL_BY_APP.items(),key=operator.itemgetter(1),reverse=True)
407                print "\n~~~~~~~~~~~~~~~~~~~\n~~RESUME BY APPS:~~\n~~~~~~~~~~~~~~~~~~~\n"
408                for x in sorted_apps[0:20]:
409                    print "\t\t{}:{}".format(x[0],x[1])
410          elapsed_time=time.time()-start_time
411          print "\nEND DATE ROUND {}/{}\nCLIENTS UPDATED:{}\nTOTAL_CLIENTS:{}\nSUCCESS:{} req\nFAILED:{} req\nREDO:{} req\nELAPSED:{} secs\n{} req/s\n".format(k+1,DATE_ROUNDS,UPDATED_TOTAL,NCLIENTS+UPDATED_TOTAL,SUCCESS,FAILED,REDO,int(elapsed_time),int(SUCCESS/elapsed_time))
412          if PAUSE_WITH_DATE_ROUNDS and DATE_ROUNDS > 1 and k != DATE_ROUNDS -1:
413            PAUSE_SHOW_STATS=True
414            readed=None
415            while readed != '\n':
416                print "Now change date into server and press enter"
417                readed=sys.stdin.readline()
418            PAUSE_SHOW_STATS=False
419          '''
420          print "\n~~~~~~~~~~~~~~~~~~~\n~~~~  RESUME:  ~~~~\n~~~~~~~~~~~~~~~~~~~\n"
421          #print(json.dumps(DATE_DATA))
422          #print ""
423          k=0
424          for l in DATE_DATA:
425             #print "another round {}".format(k)
426             if k==0:
427                continue;
428             for r in l:
429                 #print "release: {}".format(r)
430                 d=l[r]
431                 for f in d:
432                     #print "flavour {}".format(f)
433                     for app in d[f]:
434                         value=d[f][app]
435                         #print "\t\t{}: {}".format(app,d[f][t])
436                         if app in l[0][r][f]:
437                            DATE_DATA[0][r][f]+=value
438                         else:
439                            DATE_DATA[0][r][f]=value
440             k+=1
441          for r in DATE_DATA[0]:
442            print "TOTAL Release {}".format(r)
443            for f in DATE_DATA[0][r]:
444               sorted_x=sorted(DATE_DATA[0][r][f].items(),key=operator.itemgetter(1),reverse=True)
445               print "\tFlavour {}".format(f)
446               for x in sorted_x[0:10]:
447                  print "\t\t{}: {}".format(x[0],x[1])
448'''
449
450
451
452    def thread_code(self,client):
453            global exit_threads
454
455            if exit_threads and exit_treads==1:
456                return
457
458            global APPS_SENT,TMP,ALL_BY_APP
459            global NPET,SUCCESS,FAILED,REDO,UPDATED
460
461            data,napps,lapps=client.get_data()
462
463           
464           
465            try:
466                do=False
467                try:
468                    r = self.sess.post(HOST,data=data,headers=HEADERS,timeout=TIMEOUT)
469                except:
470                    do=True
471                    time.sleep(SLEEP_THREAD_TIME)
472                if do:
473                    self.lock.acquire()
474                    REDO+=1
475                    self.lock.release()
476                    r = self.sess.post(HOST,data=data,headers=HEADERS,timeout=TIMEOUT)
477
478                #sys.stderr.write('.')
479
480                if SLEEP_THREAD_TIME and SLEEP_THREAD_TIME > 0:
481                    time.sleep(SLEEP_THREAD_TIME)
482               
483                if r.text != 'OK':
484                    self.lock.acquire()
485                    FAILED+=1
486                    self.lock.release()
487                    raise Exception('Fail Sending')
488                self.lock.acquire()
489                NPET+=1
490                SUCCESS+=1
491                APPS_SENT[str(client.release)][client.real_flavour] = APPS_SENT[str(client.release)][client.real_flavour] + napps
492                for app in lapps:
493                    if app not in ALL_BY_APP:
494                        ALL_BY_APP[app]=int(lapps[app])
495                    else:
496                        ALL_BY_APP[app]+=int(lapps[app])
497                    if app not in APPS_SENT[str(client.release)][client.real_flavour+'apps']:
498                        APPS_SENT[str(client.release)][client.real_flavour+'apps'][app]=int(lapps[app])
499                    else:
500                        APPS_SENT[str(client.release)][client.real_flavour+'apps'][app]+=int(lapps[app])
501                self.lock.release()
502
503            except Exception as e:
504                sys.stderr.write('E'+str(e))
505
506            if LOOPS > 1 and CLIENT_UPDATE_FREQ > random.randint(0,100):
507                self.lock.acquire()
508                UPDATED+=1
509                self.lock.release()
510                client.update()
511
512if __name__ == "__main__":
513    tst = TEST()
514    tst.init_test()
Note: See TracBrowser for help on using the repository browser.