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