blob: b1c345f695f6317594fd170449bb429273eafd25 [file] [log] [blame]
inikep9470b872016-06-09 12:54:06 +02001#! /usr/bin/env python
2# execute(), fetch(), notify() are based on https://github.com/getlantern/build-automation/blob/master/build.py
3
4import argparse
5import os
6import string
7import time
8import traceback
9from subprocess import Popen, PIPE
10
11repo_url = 'https://github.com/Cyan4973/zstd.git'
12test_dir_name = 'speedTest'
13
14
15def log(text):
16 print time.strftime("%Y/%m/%d %H:%M:%S") + ' - ' + text
17
18def execute(command, print_output=True):
19 log("> " + command)
20 popen = Popen(command, stdout=PIPE, stderr=PIPE, shell=True, cwd=execute.cwd)
21 itout = iter(popen.stdout.readline, b"")
22 iterr = iter(popen.stderr.readline, b"")
23 stdout_lines = list(itout)
24 if print_output:
25 print ''.join(stdout_lines)
26 stderr_lines = list(iterr)
27 if stderr_lines:
28 print ''.join(stderr_lines)
29 popen.communicate()
30 if popen.returncode is not None and popen.returncode != 0:
31 raise RuntimeError(''.join(stderr_lines))
32 return stdout_lines + stderr_lines
33execute.cwd = None
34
35
36def fetch():
37 execute('git fetch -p')
38 output = execute('git branch -rl')
39 for line in output:
40 if "HEAD" in line:
41 output.remove(line) # remove "origin/HEAD -> origin/dev"
42 branches = map(lambda l: l.strip(), output)
43 return map(lambda b: (b, execute('git show -s --format=%h ' + b)[0].strip()), branches)
44
45
46def notify(branch, commit, last_commit):
47 text_tmpl = string.Template('Changes since $last_commit:\r\n$commits')
48 branch = branch.split('/')[1]
49 fmt = '--format="%h: (%an) %s, %ar"'
50 if last_commit is None:
51 commits = execute('git log -n 10 %s %s' % (fmt, commit))
52 else:
53 commits = execute('git --no-pager log %s %s..%s' % (fmt, last_commit, commit))
54
55 text = text_tmpl.substitute({'last_commit': last_commit, 'commits': ''.join(commits)})
56 print str("commits for %s: %s" % (commit, text))
57
58
59def compile(branch, commit, dry_run):
60 local_branch = string.split(branch, '/')[1]
61 version = local_branch.rpartition('-')[2]
62 version = version + '_' + commit
63 execute('git checkout -- . && git checkout ' + branch)
64 if not dry_run:
65 execute('VERSION=' + version + '; make clean zstdprogram')
66
67
68def get_last_commit(resultsFileName):
69 if not os.path.isfile(resultsFileName):
70 return None, None, None
71 commit = None
72 cspeed = []
73 dspeed = []
74 with open(resultsFileName,'r') as f:
75 for line in f:
76 words = line.split()
77 if len(words) == 2: # branch + commit
78 commit = words[1];
79 cspeed = []
80 dspeed = []
81 if (len(words) == 8):
82 cspeed.append(float(words[3]))
83 dspeed.append(float(words[5]))
84 #if commit != None:
85 # print "commit=%s cspeed=%s dspeed=%s" % (commit, cspeed, dspeed)
86 return commit, cspeed, dspeed
87
88
89def benchmark_and_compare(branch, commit, resultsFileName, lastCLevel, testFilePath, fileName, last_cspeed, last_dspeed, lower_limit, maxLoadAvg):
90 while os.getloadavg()[0] > maxLoadAvg:
91 print "bench loadavg=%.2f is higher than %s" % (os.getloadavg()[0], maxLoadAvg)
92 time.sleep(30)
93 start_load = str(os.getloadavg())
94 result = execute('programs/zstd -qb1e' + str(lastCLevel) + ' ' + testFilePath)
95 end_load = str(os.getloadavg())
96 linesExpected = lastCLevel + 2;
97 if len(result) != linesExpected:
98 print "len(result)=%d is different that expected %d" % (len(result), linesExpected)
99 return ""
100 with open(resultsFileName, "a") as myfile:
101 myfile.write(branch + " " + commit + "\n")
102 myfile.writelines(result)
103 myfile.close()
104 if (last_cspeed == None):
105 return ""
106 commit, cspeed, dspeed = get_last_commit(resultsFileName)
107 text = ""
108 for i in range(0, min(len(cspeed), len(last_cspeed))):
109 if (cspeed[i]/last_cspeed[i] < lower_limit):
110 text += "WARNING: File=%s level=%d cspeed=%s last=%s diff=%s\n" % (fileName, i+1, cspeed[i], last_cspeed[i], cspeed[i]/last_cspeed[i])
111 if (dspeed[i]/last_dspeed[i] < lower_limit):
112 text += "WARNING: File=%s level=%d dspeed=%s last=%s diff=%s\n" % (fileName, i+1, dspeed[i], last_dspeed[i], dspeed[i]/last_dspeed[i])
113 if text:
114 text += "maxLoadAvg=%s load average at start=%s end=%s\n" % (maxLoadAvg, start_load, end_load)
115 return text
116
117
118def send_email(branch, commit, last_commit, emails, text, results_files, logFileName, lower_limit):
119 with open(logFileName, "w") as myfile:
120 myfile.writelines(text)
121 myfile.close()
122 execute("mutt -s \"[ZSTD_speedTest] Warning for branch=" + branch + " commit=" + commit + " last_commit=" + last_commit + " speed<" + str(lower_limit) + "\" " + emails + " -a " + results_files + " < " + logFileName)
123
124
125def main(args, test_path, clone_path, testFilePaths):
126 print "test_path=%s" % test_path
127 print "clone_path=%s" % clone_path
128 print "testFilePath(%s)=%s" % (len(testFilePaths), testFilePaths)
129 print "emails=%s" % args.emails
130 print "maxLoadAvg=%s" % args.maxLoadAvg
131 print "lowerLimit=%s" % args.lowerLimit
132 print "lastCLevel=%s" % args.lastCLevel
133 print "sleepTime=%s" % args.sleepTime
134 print "dry_run=%s" % args.dry_run
135
136 for branch, commit in fetch():
137 log("checking branch %s: head %s" % (branch, commit))
138 try:
139 commitFileName = test_path + "/commit_" + branch.replace("/", "_")
140 if os.path.isfile(commitFileName):
141 last_commit = file(commitFileName, 'r').read()
142 else:
143 last_commit = None
144 file(commitFileName, 'w').write(commit)
145
146 if commit == last_commit:
147 log("skipping branch %s: head %s already processed" % (branch, commit))
148 else:
149 log("build branch %s: head %s is different from prev %s" % (branch, commit, last_commit))
150 compile(branch, commit, args.dry_run)
151
152 logFileName = test_path + "/log_" + branch.replace("/", "_")
153 text_to_send = []
154 results_files = ""
155 for filePath in testFilePaths:
156 fileName = filePath.rpartition('/')[2]
157 resultsFileName = test_path + "/results_" + branch.replace("/", "_") + "_" + fileName
158 last_commit, cspeed, dspeed = get_last_commit(resultsFileName)
159
160 if not args.dry_run:
161 text = benchmark_and_compare(branch, commit, resultsFileName, args.lastCLevel, filePath, fileName, cspeed, dspeed, args.lowerLimit, args.maxLoadAvg)
162 if text:
163 text = benchmark_and_compare(branch, commit, resultsFileName, args.lastCLevel, filePath, fileName, cspeed, dspeed, args.lowerLimit, args.maxLoadAvg)
164 if text:
165 text_to_send.append(text)
166 results_files += resultsFileName + " "
167 if text_to_send:
168 send_email(branch, commit, last_commit, args.emails, text_to_send, results_files, logFileName, args.lowerLimit)
169 notify(branch, commit, last_commit)
170 except Exception as e:
171 stack = traceback.format_exc()
172 log("Error build %s, error %s" % (branch, str(e)) )
173
174
175if __name__ == '__main__':
176 parser = argparse.ArgumentParser()
177 parser.add_argument('testFileNames', help='file names list for speed test')
178 parser.add_argument('emails', help='e-mails to send warnings')
179 parser.add_argument('--lowerLimit', type=float, help='send email if speed is lower than given limit e.g. 0.98', default=0.98)
180 parser.add_argument('--maxLoadAvg', type=float, help='maximum load average to start testing', default=0.75)
181 parser.add_argument('--lastCLevel', type=int, help='last compression level for testing', default=5)
182 parser.add_argument('--sleepTime', type=int, help='frequency of checking in seconds', default=60)
183 parser.add_argument('--dry-run', dest='dry_run', action='store_true', help='not build', default=False)
184 args = parser.parse_args()
185
186 # check if test files are accessible
187 testFileNames = args.testFileNames.split()
188 testFilePaths = []
189 for fileName in testFileNames:
190 if os.path.isfile(fileName):
191 testFilePaths.append(os.path.abspath(fileName))
192 else:
193 raise RuntimeError("File not found: " + fileName)
194
195 test_path = os.getcwd() + '/' + test_dir_name # /path/to/zstd/tests/speedTest
196 clone_path = test_path + '/' + 'zstd' # /path/to/zstd/tests/speedTest/zstd
inikep9470b872016-06-09 12:54:06 +0200197
198 # clone ZSTD repo if needed
199 if not os.path.isdir(test_path):
200 os.mkdir(test_path)
201 if not os.path.isdir(clone_path):
inikep348a53a2016-06-09 13:14:21 +0200202 execute.cwd = test_path
inikep9470b872016-06-09 12:54:06 +0200203 execute('git clone ' + repo_url)
204 if not os.path.isdir(clone_path):
205 raise RuntimeError("ZSTD clone not found: " + clone_path)
inikep348a53a2016-06-09 13:14:21 +0200206 execute.cwd = clone_path
inikep9470b872016-06-09 12:54:06 +0200207
208 while True:
209 pid = str(os.getpid())
210 pidfile = "./speedTest.pid"
211 if os.path.isfile(pidfile):
212 print "%s already exists, exiting" % pidfile
213 else:
214 file(pidfile, 'w').write(pid)
215 try:
216 loadavg = os.getloadavg()[0]
217 if (loadavg <= args.maxLoadAvg):
218 main(args, test_path, clone_path, testFilePaths)
219 else:
220 print "loadavg=%.2f is higher than %s" % (loadavg, args.maxLoadAvg)
221 finally:
222 os.unlink(pidfile)
223 time.sleep(args.sleepTime)