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

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

update test_client

  • Property svn:executable set to *
File size: 19.3 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
37MAX_RANDOM_CLIENTS=50000
38DEBUG=0
39SHOW_BY_APP=True
40PAUSE_SHOW_STATS=False
41PAUSE_WITH_DATE_ROUNDS=True
42# STAT COUNTERS
43NPET=0
44SUCCESS=0
45FAILED=0
46REDO=0
47UPDATED_TOTAL=0
48UPDATED=0
49RANDOM=True     # Random generation releases, flavours, macs
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 RANDOM and 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        if RANDOM:
144            self.release=RELEASES[random.randint(0,4)%2] # more probabilities for the first listed release
145        else:
146            self.release=RELEASES[0]
147        self.release_number=self.gen_rel_number(self.release)
148        if RANDOM:
149            fla=FLAVOURS[self.release][random.randint(0,len(FLAVOURS[self.release])-1)]
150        else:
151            fla=FLAVOURS[self.release][0]
152        self.flavour=fla['name']
153        self.real_flavour=fla['real']
154        self.updates=0
155        self.inc_date=0
156
157    def info(self):
158        print '{} {} {} {}'.format(self.mac,self.release,self.release_number,self.flavour)
159
160    def gen_mac(self):
161        global NCLIENTS,MAX_RANDOM_CLIENTS,MACS_USED
162
163        if RANDOM and NCLIENTS <= MAX_RANDOM_CLIENTS:
164            mac = "02:00:00:%02x:%02x:%02x" % (random.randint(0, 255),random.randint(0, 255),random.randint(0, 255))
165            while mac in MACS_USED:
166                mac = "02:00:00:%02x:%02x:%02x" % (random.randint(0, 255),random.randint(0, 255),random.randint(0, 255))
167            MACS_USED.append(mac)
168            return mac
169        else:
170            mac = MAC_LIST.popleft()
171            return mac
172
173    def gen_rel_number(self,release,dummy=False):
174        if not RANDOM:
175            dummy=True
176        if release == '15':
177            if not dummy:
178                self.num=random.randint(100,100+MAX_RELNUMBER)
179            else:
180                self.num=100
181            rnum='15.05.0.{}'.format(self.num)
182        else:
183            if not dummy:
184                self.year=random.randint(16,16+(MAX_RELNUMBER/12))
185                self.month=random.randint(1,1+(MAX_RELNUMBER%12))
186                self.day=1
187            else:
188                self.year=16
189                self.month=1
190                self.day=1
191            rnum='16.{}{:02d}{:02d}'.format(self.year,self.month,self.day)
192        return rnum
193
194    def gen_apps(self):
195        global RANDOM,CLIENT_MAX_APPS,NAPPS
196        l={}
197        if RANDOM:
198            napps=random.randint(0,CLIENT_MAX_APPS)
199        else:
200            napps=CLIENT_MAX_APPS
201            start_app_at=random.randint(1,NAPPS-CLIENT_MAX_APPS+1)
202        for i in range(napps):
203            if RANDOM:
204                apprandname='app'+str(random.randint(1,NAPPS))
205                while apprandname in l:
206                    apprandname='app'+str(random.randint(1,NAPPS))
207                l[apprandname]=str(random.randint(1,10))
208            else:
209                apprandname='app'+str(start_app_at+i)
210                l[apprandname]=10
211        return (l,napps)
212
213    def update(self):
214        if self.release == '15':
215            self.num = self.num +1
216        else:
217            self.month = self.month +1
218        self.updates = self.updates +1
219        self.release_number=self.gen_rel_number(self.release,True)
220       
221    def get_data(self):
222        tmp={}
223        tmp['sab']=self.flavour
224        tmp['vers']=self.release_number
225        apps,napps=self.gen_apps()
226        tmp['stats']=json.dumps(apps)
227        tmp['uid']=self.mac
228        if self.inc_date > 0:
229            d=datetime.today()+relativedelta(months=0-self.inc_date)
230            tmp['date']=d.strftime('%Y-%m-%d %H:%M:%S')
231        return ({'stats':json.dumps(tmp)},napps,apps)
232
233class TEST():
234    def __init__(self):
235        self.client_list = deque()
236        self.db = DB()
237        self.stats={}
238        self.pool=None
239        self.lock=Lock()
240        signal.signal(signal.SIGTERM,self.term_test)
241        signal.signal(signal.SIGINT,self.term_test)
242
243    def term_test(self,*args,**kwargs):
244        global STATS_ON
245        global exit_threads
246        exit_threads=1
247        STATS_ON=0
248        print "Exitting.."
249        sys.exit(1)
250
251    def prepare(self):
252        print 'Preparing clients... '
253        pad=len(str(NCLIENTS))
254        clean='\r\r\r\r\r\r\r\r\r\r';
255        for i in range(NCLIENTS):
256            sys.stderr.write(clean+str(i+1).zfill(pad))
257            cli=CLIENT()
258            #cli.info()
259            self.client_list.append(cli)
260        sys.stderr.write(clean+str(NCLIENTS)+' generated!\n')
261        self.do_stats()
262        self.print_stats()
263
264    def prepare_threads(self):
265        global MAX_THREADS
266        if (NCLIENTS < MAX_THREADS):
267            MAX_THREADS=NCLIENTS
268            print("NEW NTHREADS {}".format(MAX_THREADS))
269        self.pool = ThreadPool(processes=MAX_THREADS)
270        sys.stderr.write('Created pool for {} threads, {} clients per thread\n'.format(MAX_THREADS,int(NCLIENTS/MAX_THREADS)))
271        self.sess = requests.Session()
272        adapter = requests.adapters.HTTPAdapter(pool_connections=CACHE_CONNECTIONS,pool_maxsize=MAX_THREADS+1,max_retries=3,pool_block=False)
273        self.sess.mount('http://',adapter)
274
275    def init_test(self):
276        global STATS_ON
277        self.prepare()
278        #self.db.init_db()
279        #self.db.close_db()
280        self.prepare_threads()
281        if STATS_ON and STATS_ON != 0:
282            STATS_ON=1
283            try:
284                threading.Thread(target=self.time_stats).start()
285            except:
286                pass
287        self.start()
288        STATS_ON=0
289
290    def do_stats(self):
291        for rel in RELEASES:
292            self.stats[rel]={}
293            self.stats[rel]['nclients']=0
294            self.stats[rel]['nclients_updated']=0
295            self.stats[rel]['flavours']={}
296
297        for cli in self.client_list:
298            self.stats[cli.release]['nclients']=self.stats[cli.release]['nclients']+1
299            self.stats[cli.release]['nclients_updated']=self.stats[cli.release]['nclients_updated']+cli.updates
300            if cli.real_flavour not in self.stats[cli.release]['flavours']:
301                self.stats[cli.release]['flavours'][cli.real_flavour]=1
302            else:
303                self.stats[cli.release]['flavours'][cli.real_flavour]=self.stats[cli.release]['flavours'][cli.real_flavour]+1
304
305    def print_stats(self):
306        print ''
307        for rel in RELEASES:
308            print 'RELEASE {}: {} clients ({} updated)'.format(rel,self.stats[rel]['nclients'],self.stats[rel]['nclients_updated'])
309            keys=self.stats[rel]['flavours'].keys()
310            keys.sort()
311            for key in keys:
312                print '\t{} : {}'.format(key,self.stats[rel]['flavours'][key])
313            print ''
314
315
316    def time_stats(self):
317        global STATS_ON,NPET,PAUSE_SHOW_STATS,SUCCESS,DATE_ROUNDS
318        global NCLIENTS,LOOPS
319        print "STATS_ON"
320        total=NCLIENTS*LOOPS
321        old_npet=[]*9
322        f=lambda x,y: x+y
323        while STATS_ON and STATS_ON != 0:
324            if not PAUSE_SHOW_STATS:
325                old_npet.append(NPET)
326                med_npet=reduce(f,old_npet)/len(old_npet)
327                old_npet=old_npet[1-len(old_npet):]
328                sys.stderr.write(' '*20+'\r'*20+'{} r/s ETA:{} secs'.format(int(med_npet),int((total-SUCCESS)/(med_npet+1))))
329                self.lock.acquire()
330                NPET=0
331                self.lock.release()
332            time.sleep(1)
333
334    def start(self):
335        global APPS_SENT,ALL_BY_APP,PAUSE_SHOW_STATS,PAUSE_WITH_DATE_ROUNDS
336        global STATS_ON,SHOW_BY_APP,NPET,SUCCESS,FAILED,REDO,UPDATED,UPDATED_TOTAL
337
338
339        #DATE_DATA=[]
340        ALL_BY_APP={}
341        for k in range(DATE_ROUNDS):
342          NPET=0
343          SUCCESS=0
344          FAILED=0
345          REDO=0
346          UPDATED=0
347          start_time=time.time()
348          if (k > 0):
349            for cli in self.client_list:
350                cli.inc_date+=1
351          TOTAL_SENT={}
352          ALL_SENT={}
353          for rel in ['15','16']:
354            TOTAL_SENT[rel]={}
355            ALL_SENT[rel]={}
356            for fla in ['desktop','server','client','other']:
357                TOTAL_SENT[rel][fla]=0
358                ALL_SENT[rel][fla]=0
359                ALL_SENT[rel][fla+'apps']={}
360
361          for i in range(LOOPS):
362              UPDATED_TOTAL=UPDATED
363              sys.stderr.write('\nRound {}/{} Date round {}/{} ...\n'.format(i+1,LOOPS,k+1,DATE_ROUNDS))
364              APPS_SENT={}
365              for rel in ['15','16']:
366                  APPS_SENT[rel]={}
367                  for fla in ['desktop','server','client','other']:
368                      APPS_SENT[rel][fla]=0
369                      APPS_SENT[rel][fla+'apps']={}
370
371              self.pool.map_async(self.thread_code,self.client_list).get(timeout=1000000)
372
373              for rel in ['15','16']:
374                  for fla in ['desktop','server','client','other']:
375                      if not STATS_ON and DEBUG and DEBUG > 1:
376                         sys.stderr.write('\t{}:{} ({} apps sent ((tmp_packages count)insert or update))\n'.format(rel,fla,APPS_SENT[rel][fla]))
377                      TOTAL_SENT[rel][fla]=TOTAL_SENT[rel][fla]+APPS_SENT[rel][fla]
378                      ALL_SENT[rel][fla]=ALL_SENT[rel][fla]+APPS_SENT[rel][fla]
379                      for app in APPS_SENT[rel][fla+'apps']:
380                          if app not in ALL_SENT[rel][fla+'apps']:
381                              ALL_SENT[rel][fla+'apps'][app]=APPS_SENT[rel][fla+'apps'][app]
382                          else:
383                              ALL_SENT[rel][fla+'apps'][app]+=APPS_SENT[rel][fla+'apps'][app]
384              if not STATS_ON:
385                sys.stderr.write('\n')
386
387          #STATS_ON=0
388          total=0
389          print "\n\nTOTAL APPS SENT AFTER DATE_ROUND {}/{}\n".format(k+1,DATE_ROUNDS)
390          other_all_sent={}
391          #mini_date_data={}
392          for rel in ['15','16']:
393              total_rel=0
394              #mini_date_data[rel]={}
395              for fla in ['desktop','server','client','other']:
396                  print "\tBY {} {} : {} sent (insert or update)(tmp_packages count)".format(rel,fla,ALL_SENT[rel][fla])
397                  total_rel=total_rel+ALL_SENT[rel][fla]
398                  if fla == 'other':
399                      for x in ALL_SENT[rel][fla+'apps']:
400                          if x in other_all_sent:
401                              other_all_sent[x]+=ALL_SENT[rel][fla+'apps'][x]
402                          else:
403                              other_all_sent[x]=ALL_SENT[rel][fla+'apps'][x]
404                  else:
405                      sorted_x=sorted(ALL_SENT[rel][fla+'apps'].items(),key=operator.itemgetter(1),reverse=True)
406                      #mini_date_data[rel][fla]=ALL_SENT[rel][fla+'apps'];
407                      for x in sorted_x[0:10]:
408                        print "\t\t{}:{}".format(x[0],x[1])
409              print "\tTOTAL RELEASE {} : {} sent (insert or update)(tmp_packages count)".format(rel,total_rel)
410              total=total+total_rel
411         
412          print "\tOTHER (combined 15,16):"
413          sorted_x=sorted(other_all_sent.items(),key=operator.itemgetter(1),reverse=True)
414          for x in sorted_x[0:10]:
415              print "\t\t{}:{}".format(x[0],x[1])
416          #mini_date_data['other']={'other':other_all_sent}
417          #DATE_DATA.append(mini_date_data)
418          print "TOTAL ALL RELEASES : {} sent (insert or updated)(tmp_packages count)".format(total)
419          if SHOW_BY_APP:
420                sorted_apps=sorted(ALL_BY_APP.items(),key=operator.itemgetter(1),reverse=True)
421                print "\n~~~~~~~~~~~~~~~~~~~\n~~RESUME BY APPS:~~\n~~~~~~~~~~~~~~~~~~~\n"
422                for x in sorted_apps[0:20]:
423                    print "\t\t{}:{}".format(x[0],x[1])
424          elapsed_time=time.time()-start_time
425          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))
426          if PAUSE_WITH_DATE_ROUNDS and DATE_ROUNDS > 1 and k != DATE_ROUNDS -1:
427            PAUSE_SHOW_STATS=True
428            readed=None
429            while readed != '\n':
430                print "Now change date into server and press enter"
431                readed=sys.stdin.readline()
432            PAUSE_SHOW_STATS=False
433          '''
434          print "\n~~~~~~~~~~~~~~~~~~~\n~~~~  RESUME:  ~~~~\n~~~~~~~~~~~~~~~~~~~\n"
435          #print(json.dumps(DATE_DATA))
436          #print ""
437          k=0
438          for l in DATE_DATA:
439             #print "another round {}".format(k)
440             if k==0:
441                continue;
442             for r in l:
443                 #print "release: {}".format(r)
444                 d=l[r]
445                 for f in d:
446                     #print "flavour {}".format(f)
447                     for app in d[f]:
448                         value=d[f][app]
449                         #print "\t\t{}: {}".format(app,d[f][t])
450                         if app in l[0][r][f]:
451                            DATE_DATA[0][r][f]+=value
452                         else:
453                            DATE_DATA[0][r][f]=value
454             k+=1
455          for r in DATE_DATA[0]:
456            print "TOTAL Release {}".format(r)
457            for f in DATE_DATA[0][r]:
458               sorted_x=sorted(DATE_DATA[0][r][f].items(),key=operator.itemgetter(1),reverse=True)
459               print "\tFlavour {}".format(f)
460               for x in sorted_x[0:10]:
461                  print "\t\t{}: {}".format(x[0],x[1])
462'''
463
464
465
466    def thread_code(self,client):
467            global exit_threads
468
469            if exit_threads and exit_treads==1:
470                return
471
472            global APPS_SENT,TMP,ALL_BY_APP
473            global NPET,SUCCESS,FAILED,REDO,UPDATED
474
475            data,napps,lapps=client.get_data()
476
477           
478           
479            try:
480                do=False
481                try:
482                    r = self.sess.post(HOST,data=data,headers=HEADERS,timeout=TIMEOUT)
483                except:
484                    do=True
485                    time.sleep(SLEEP_THREAD_TIME)
486                if do:
487                    self.lock.acquire()
488                    REDO+=1
489                    self.lock.release()
490                    r = self.sess.post(HOST,data=data,headers=HEADERS,timeout=TIMEOUT)
491
492                #sys.stderr.write('.')
493
494                if SLEEP_THREAD_TIME and SLEEP_THREAD_TIME > 0:
495                    time.sleep(SLEEP_THREAD_TIME)
496               
497                if r.text != 'OK':
498                    self.lock.acquire()
499                    FAILED+=1
500                    self.lock.release()
501                    raise Exception('Fail Sending')
502                self.lock.acquire()
503                NPET+=1
504                SUCCESS+=1
505                APPS_SENT[str(client.release)][client.real_flavour] = APPS_SENT[str(client.release)][client.real_flavour] + napps
506                for app in lapps:
507                    if app not in ALL_BY_APP:
508                        ALL_BY_APP[app]=int(lapps[app])
509                    else:
510                        ALL_BY_APP[app]+=int(lapps[app])
511                    if app not in APPS_SENT[str(client.release)][client.real_flavour+'apps']:
512                        APPS_SENT[str(client.release)][client.real_flavour+'apps'][app]=int(lapps[app])
513                    else:
514                        APPS_SENT[str(client.release)][client.real_flavour+'apps'][app]+=int(lapps[app])
515                self.lock.release()
516
517            except Exception as e:
518                sys.stderr.write('E'+str(e))
519
520            if LOOPS > 1 and CLIENT_UPDATE_FREQ > random.randint(0,100):
521                self.lock.acquire()
522                UPDATED+=1
523                self.lock.release()
524                client.update()
525
526if __name__ == "__main__":
527    tst = TEST()
528    tst.init_test()
Note: See TracBrowser for help on using the repository browser.