Completed
Push — master ( 877045...21857a )
by dup
59s
created

Options.parse_command_line()   F

Complexity

Conditions 10

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
dl 0
loc 37
rs 3.1304
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like Options.parse_command_line() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
# -*- coding: utf8 -*-
3
#
4
#  flexlm_analysis.py : Script to analyse flexlm log files
5
#
6
#  (C) Copyright 2013 Olivier Delhomme
7
#  e-mail : [email protected]
8
#
9
#  This program is free software; you can redistribute it and/or modify
10
#  it under the terms of the GNU General Public License as published by
11
#  the Free Software Foundation; either version 2, or (at your option)
12
#  any later version.
13
#
14
#  This program is distributed in the hope that it will be useful,
15
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
#  GNU General Public License for more details.
18
#
19
#  You should have received a copy of the GNU General Public License
20
#  along with this program; if not, write to the Free Software Foundation,
21
#  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
#
23
import os
24
import sys
25
import getopt
26
import shutil
27
import argparse
28
import re
29
import gzip
30
31
32
class Options:
33
    """
34
    A class to manage command line options
35
    """
36
37
    files = []          # file list that we will read looking for entries
38
    out = 'None'        # Character string to choose which output we want:
39
                        # stat or gnuplot
40
    image = 'image.png' # Image name to be included in the gnuplot script
41
    description = {}
42
    options = None
43
44
    def __init__(self):
45
        """
46
        Inits the class
47
        """
48
        self.files = []
49
        self.out = 'None'
50
        self.image ='image.png'
51
        self.description = {}
52
        self.options = None
53
54
        self._get_command_line_arguments()
55
56
    # End of init() function
57
58
59
    def _get_command_line_arguments(self):
60
        """
61
        Defines and gets all the arguments for the command line using
62
        argparse module. This function is called in the __init__ function
63
        of this class.
64
        """
65
        str_version = 'flexlm_analysis'
66
67
        parser = argparse.ArgumentParser(description='Script to analyse flexlm log files.', version=str_version)
68
69
        parser.add_argument('-i', '--image', action='store', dest='image', help='Tells the image name the gnuplot script may generate', default='image.png')
70
        parser.add_argument('-g', '--gnuplot', action='store_true', dest='gnuplot', help='Outputs a gnuplot script that can be executed later to generate an image about the usage', default=False)
71
        parser.add_argument('-s', '--stats', action='store_true', dest='stats', help='Outputs some stats about the use of the modules as stated in the log file', default=False)
72
        parser.add_argument('files', metavar='Files', type=str, nargs='+', help='Log files to be parsed')
73
    
74
75
        self.options = parser.parse_args()
76
        
77
        if self.options.gnuplot:
78
            self.out = 'gnuplot'
79
80
        if self.options.stats:
81
            self.out = 'stat'
82
83
        self.image = self.options.image
84
        self.files = self.options.files
85
86
    # End of get_command_line_arguments() function
87
# End of Conf class
88
89
90
91
92
93
def read_files(files):
94
    """Matches lines in the files and returns the number of days in use and a
95
    list of the following tuple : (date, time, state, module, user, machine).
96
    """
97
98
99
# 8:21:20 (lmgrd) FLEXnet Licensing (v10.8.0 build 18869) started on MACHINE (IBM PC) (7/30/2012)
100
101
    result_list = []
102
    nb_days = 0
103
    date_matched = False
104
105
    # Reading each file here, one after the other.
106
    for a_file in files:
107
108
        # Openning the files with gzip if they end with .gz, in normal mode
109
        # if not. Do we need bz2 ? May be we should do this with a magic number
110
        if '.gz' in a_file:
111
            inode = gzip.open(a_file, 'r')
112
        else:
113
            inode = open(a_file, 'r')
114
115
        date = '??/??/????'  # Some unknown date
116
        old_date = ''
117
118
        for line in inode:
119
            # Looking for some patterns in the file
120
121
            if date_matched == False:
122
                # Look for the line below to guess the starting date (here : 2012/7/30)
123
                # 8:21:20 (lmgrd) FLEXnet Licensing (v10.8.0 build 18869) started on MACHINE (IBM PC) (7/30/2012)
124
                res = re.match(r'\ ?\d+:\d+:\d+ .+ \((\d+)/(\d+)/(\d+)\)', line)
125
                if res:
126
                    # Formating like this : year/month/day
127
                    date = '%s/%s/%s' % (res.group(3), res.group(1), res.group(2))
128
                    date_matched = True
129
130
            # 11:50:22 (orglab) OUT: "Origin7" user@MACHINE-NAME
131
            res = re.match(r'\ ?(\d+:\d+:\d+) .+ (\w+): "(.+)" (\w+)@(.+)', line)
132
            if res:
133
                # I put the results in variables for clarity
134
                time = res.group(1)
135
                state = res.group(2)
136
                module = res.group(3)
137
                user = res.group(4)
138
                machine = res.group(5)
139
                result_list.append((date, time, state, module, user, machine))
140
            else:
141
                # Trying this patern instead :
142
                # 20:52:29 (lmgrd) TIMESTAMP 1/23/2012
143
                res = re.match(r'\ ?\d+:\d+:\d+ .+ TIMESTAMP (\d+)\/(\d+)\/(\d+)', line)
144
                if res:
145
                    # Formating like this : year/month/day
146
                    date = '%s/%s/%s' % (res.group(3), res.group(1), res.group(2))
147
                    if date != old_date:
148
                        nb_days = nb_days + 1
149
                        old_date = date
150
151
        inode.close()
152
153
    # Returning the total number of days seen in the log file and the
154
    # list containing all tuples
155
    return (nb_days, result_list)
156
157
# End of read_files function
158
159
160
def do_some_stats(result_list):
161
    """Here we do some stats and fill a dictionnary of stats that will
162
    contain all stats per module ie one tuple containing the following :
163
    (max_users, min_users, max_day, nb_users, total_use, nb_days).
164
165
    Returns a dictionnary of statistics and a list of module that has stats.
166
    """
167
168
    old_date = 'XXXXXXX'  # A value that does not exists
169
    # nb_days = 0           # Total number of days of use
170
171
    module_list = []
172
    stats = dict()  # Tuple dictionnary : (max_users, min_users, max_day, nb_users, total_use, nb_days)
173
174
    for data in result_list:
175
        (date, time, state, module, user, machine) = data
176
177
        if module not in module_list:
178
            # Initialiasing the new module with default values
179
            module_list.append(module)
180
            stats[module] = (0, 999999999999, '', -1, 0, 0)
181
182
        # Retreiving the values for a specific module
183
        (max_users, min_users, max_day, nb_users, total_use, nb_days) = stats[module]
184
185
        # Calculating statistics
186
        if date != old_date:
187
188
            if nb_users < 0:
189
                nb_users = 1
190
191
            nb_days = nb_days + 1
192
            old_date = date
193
194
        if state.lower() == 'out':
195
            nb_users = nb_users + 1
196
            total_use = total_use + 1
197
198
        elif state.lower() == 'in':
199
            nb_users = nb_users - 1
200
201
        if nb_users > 0 and nb_users > max_users:
202
            max_users = nb_users
203
            max_day = date
204
205
        if nb_users > 0  and nb_users < min_users:
206
            min_users = nb_users
207
208
        # Saving the new values
209
        stats[module] = (max_users, min_users, max_day, nb_users, total_use, nb_days)
210
211
    return (stats, module_list)
212
213
# End of do_some_stats function
214
215
216
def print_stats(nb_days, stats, name_list):
217
    """Prints the stats module per module to the screen.
218
    """
219
220
    for name in name_list:
221
        (max_users, min_users, max_day, nb_users, total_use, nb_use_days) = stats[name]
222
223
        print('Module %s :' % name)
224
        print(' Number of users per day :')
225
        print('  max : %d (%s)' % (max_users, max_day))
226
227
        if (nb_use_days > 0):
228
            print('  avg : %d' % (total_use/nb_use_days))
229
230
        print('  min : %d' % min_users)
231
232
        print(' Total number of use : %d' % total_use)
233
        print(' Number of days used : %d / %d' % (nb_use_days, nb_days))
234
        print('')  # Fancier things
235
236
# End of print_stats function
237
238
239
def output_stats(nb_days, result_list):
240
    """Does some stats on the result list and prints them on the screen.
241
    """
242
243
    (stats, name_list) = do_some_stats(result_list)
244
    print_stats(nb_days, stats, name_list)
245
246
# End of output_stats
247
248
249
def do_gnuplot_stats(result_list):
250
    """Here we do some gnuplot style stats in order to draw an image of the
251
    evolution of the use of the modules.
252
253
    event_list contains the date, time and number of used licenses in reverse
254
    chronological order.
255
    """
256
257
    module_list = []
258
    stats = dict()
259
260
    for data in result_list:
261
        (date, time, state, module, user, machine) = data
262
263
264
        if module not in module_list:
265
            module_list.append(module)
266
            stats[module] = []   # Creating an empty list of tuples for this new module.
267
268
        event_list = stats[module]
269
270
        if event_list == []:
271
            if state.lower() == 'out':
272
                use = 1
273
            elif state.lower == 'in':
274
                use = 0
275
276
            event_list.insert(0, (date, time, use))  # Prepending to the list
277
278
        else:
279
            (some_date, some_time, use) = event_list[0] # retrieving the last 'use' value to update it
280
281
            if state.lower() == 'out':
282
                use = use + 1
283
            elif state.lower() == 'in':
284
                use = use - 1
285
286
            event_list.insert(0, (date, time, use)) # Prepending to the list
287
288
        stats[module] = event_list
289
290
    return (stats, module_list)
291
292
# End of do_some_stats function
293
294
295
def print_gnuplot(image_name, nb_days, stats, module_list):
296
    """Writing the data files and the gnuplot script.
297
    """
298
299
    # Generating the gnuplot script
300
    gnuplot_file = open('gnuplot.script', 'w')
301
302
    gnuplot_file.write('set key right\n')
303
    gnuplot_file.write('set grid\n')
304
    gnuplot_file.write('set title "FlexLm"\n')
305
    gnuplot_file.write('set xdata time\n')
306
    gnuplot_file.write('set timefmt "%Y/%m/%d %H:%M:%S"\n')
307
    gnuplot_file.write('set format x "%Y/%m/%d %H:%M:%S"\n')
308
    gnuplot_file.write('set xlabel "Date"\n')
309
    gnuplot_file.write('set ylabel "Nombre d\'executions"\n')
310
    gnuplot_file.write('set output "%s"\n' % image_name)
311
    gnuplot_file.write('set style line 1 lw 1\n')
312
    gnuplot_file.write('set terminal png size %d,1024\n' % (24*nb_days))
313
    gnuplot_file.write('plot ')
314
315
    first_line = True
316
317
    # Generating data files. Their names are based upon the module name being analysed
318
    for m in module_list:
319
        dat_filename = '%s.dat' % m
320
        dat_file = open(dat_filename, 'w')
321
322
        if first_line == True:
323
            gnuplot_file.write('"%s" using 1:3 title "%s" with lines' % (dat_filename, m))
324
            first_line = False
325
        else:
326
            gnuplot_file.write(', \\\n"%s" using 1:3 title "%s" with lines' % (dat_filename, m))
327
328
        event_list = stats[m]
329
        event_list.reverse()  # We have to reverse the list as we prepended elements
330
331
        for event in event_list:
332
            (date, time, use) = event
333
            dat_file.write('%s %s %d\n' % (date, time, use))
334
335
        dat_file.close()
336
337
338
def output_gnuplot(image_name, nb_days, result_list):
339
    """Does some stats and outputs them into some data files and a gnuplot script
340
    that one might run later.
341
    """
342
343
    (stats, module_list) = do_gnuplot_stats(result_list)
344
    print_gnuplot(image_name, nb_days, stats, module_list)
345
346
# End of output_gnuplot
347
348
349
def main():
350
    """Here we choose what to do upon the command line's options.
351
    """
352
353
    # Parsing options
354
    my_opts = Options()
355
356
    print("%s - %s" % (my_opts.files, my_opts.out))
357
358
    (nb_days, result_list) = read_files(my_opts.files)
359
360
    if len(result_list) > 1 :
361
362
        if my_opts.out == 'stat':
363
            output_stats(nb_days, result_list)
364
365
        # We do not want to generate an image if the number of day usage is less than one !
366
        elif my_opts.out == 'gnuplot' and nb_days > 0:
367
            output_gnuplot(my_opts.image, nb_days, result_list)
368
369
if __name__=="__main__" :
370
    main()
371