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

Last change on this file since 6820 was 6820, checked in by mabarracus, 19 months ago

Update test client to allow platform data

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