001package org.kuali.common.devops.jenkins.scan;
002
003import static com.google.common.base.Stopwatch.createStarted;
004import static com.google.common.collect.Lists.newArrayList;
005import static com.google.common.collect.Maps.newHashMap;
006import static com.google.common.collect.Maps.newTreeMap;
007import static com.google.common.collect.Ordering.natural;
008import static java.lang.Long.parseLong;
009import static java.lang.Math.ceil;
010import static java.lang.Math.max;
011import static java.lang.String.format;
012import static java.util.Collections.reverse;
013import static java.util.Collections.sort;
014import static java.util.TimeZone.getTimeZone;
015import static org.apache.commons.io.FileUtils.readFileToString;
016import static org.apache.commons.io.FileUtils.write;
017import static org.apache.commons.io.FileUtils.writeStringToFile;
018import static org.kuali.common.core.io.Files.getCanonicalFile;
019import static org.kuali.common.devops.jenkins.scan.AggregateJobInformationTest.BUCKET;
020import static org.kuali.common.devops.jenkins.scan.AggregateJobInformationTest.REPO;
021import static org.kuali.common.devops.jenkins.scan.AggregateJobInformationTest.getKey;
022import static org.kuali.common.devops.jenkins.scan.Jobs.getCalculatedDurationStatistics;
023import static org.kuali.common.devops.jenkins.scan.Jobs.getLastBuildNumber;
024import static org.kuali.common.devops.jenkins.scan.Jobs.getRecordedDurationStatistics;
025import static org.kuali.common.devops.jenkins.scan.Jobs.getStartTimeStats;
026import static org.kuali.common.devops.jenkins.scan.Jobs.getTotalExecutions;
027import static org.kuali.common.util.Encodings.UTF8;
028import static org.kuali.common.util.FormatUtils.getCount;
029import static org.kuali.common.util.FormatUtils.getTime;
030import static org.kuali.common.util.base.Exceptions.illegalState;
031import static org.kuali.common.util.log.LoggerUtils.logTable;
032import static org.kuali.common.util.log.Loggers.newLogger;
033
034import java.io.File;
035import java.io.IOException;
036import java.text.SimpleDateFormat;
037import java.util.Date;
038import java.util.List;
039import java.util.Map;
040
041import org.junit.Ignore;
042import org.junit.Test;
043import org.kuali.common.core.json.api.JsonService;
044import org.kuali.common.core.json.jackson.JacksonJsonService;
045import org.kuali.common.util.FormatUtils;
046import org.kuali.common.util.LocationUtils;
047import org.kuali.common.util.file.CanonicalFile;
048import org.slf4j.Logger;
049import org.springframework.core.io.DefaultResourceLoader;
050import org.springframework.core.io.Resource;
051import org.springframework.core.io.ResourceLoader;
052
053import com.google.common.base.Joiner;
054import com.google.common.base.Stopwatch;
055import com.google.common.collect.ImmutableList;
056
057public class AggregatedStatisticsTest {
058
059        private static final Logger logger = newLogger();
060
061        private JsonService json = new JacksonJsonService();
062        private List<String> stacks = ImmutableList.of("prod", "rice", "cifn");
063        private static final Map<Team, List<Job>> jobsByTeam = newTreeMap();
064        private static final Map<Type, List<Job>> jobsByType = newTreeMap();
065        private static final Map<TypeAndTeam, List<Job>> jobsByTypeAndTeam = newTreeMap();
066        private final long msPerSecond = 1000;
067        private final long msPerMinute = msPerSecond * 60;
068        private final long msPerHour = msPerMinute * 60;
069        // private final long msPerDay = msPerHour * 24;
070        // private final long msPerYear = msPerDay * 365;
071        private final String timezone = "US/Eastern";
072        private final String dateFormat = "yyyy-MM";
073        private final SimpleDateFormat sdf = getSimpleDateFormat(dateFormat, timezone);
074        private final SimpleDateFormat minuteFormatter = getSimpleDateFormat("yyyyMMdd-HHmm", timezone);
075        private final SimpleDateFormat dayFormatter = getSimpleDateFormat("yyyyMMdd", timezone);
076
077        @Test
078        public void test() {
079                try {
080                        Stopwatch sw = createStarted();
081                        List<String> keys = getKeys(stacks);
082                        List<Jenkins> jenkins = getJenkins(keys);
083                        List<JobExecution> builds = getBuilds(jenkins);
084                        doBuildSpikes(builds);
085                        doHoursPerMonth(builds);
086                        info("total builds     -> %s", getCount(builds.size()));
087                        info("total build time -> %s", getTime(sumDuration(builds)));
088                        info("elapsed          -> %s", getTime(sw));
089                } catch (Throwable e) {
090                        e.printStackTrace();
091                }
092        }
093
094        private void doBuildSpikes(List<JobExecution> builds) throws Exception {
095                Map<String, Integer> map = asMap(builds);
096                info("tracked minutes -> %s", getCount(map.size()));
097                List<String> keys = natural().sortedCopy(map.keySet());
098                List<Spike> list = newArrayList();
099                for (String key : keys) {
100                        long minute = parseLong(key);
101                        long timestamp = minute * msPerMinute;
102                        int count = map.get(key);
103                        Spike element = Spike.builder().withCount(count).withTimestamp(timestamp).build();
104                        list.add(element);
105                }
106                sort(list, SpikeComparator.INSTANCE);
107                reverse(list);
108                Map<String, Integer> spikeMap = newHashMap();
109                for (Spike spike : list) {
110                        long timestamp = spike.getTimestamp();
111                        String day = dayFormatter.format(new Date(timestamp));
112                        Integer value = spikeMap.get(day);
113                        if (value == null) {
114                                value = spike.getCount();
115                        }
116                        Integer max = max(value, spike.getCount());
117                        spikeMap.put(day, max);
118                }
119                List<String> values = natural().sortedCopy(spikeMap.keySet());
120                List<String> lines = newArrayList();
121                for (String value : values) {
122                        lines.add(value + "," + spikeMap.get(value));
123                }
124                File file = getCanonicalFile("./target/jenkins/aggregated-spikes.txt");
125                info("create  -> %s", file);
126                write(file, Joiner.on('\n').join(lines));
127        }
128
129        private void doHoursPerMonth(List<JobExecution> builds) throws Exception {
130                Map<String, Long> map = asMonthlyBuildHours(builds);
131                List<String> csv = asCsv(map);
132                File file = getCanonicalFile("./target/jenkins/hours.txt");
133                info("create           -> %s", file);
134                write(file, Joiner.on('\n').join(csv));
135        }
136
137        private long sumDuration(List<JobExecution> builds) {
138                long duration = 0;
139                for (JobExecution build : builds) {
140                        duration += build.getDuration();
141                }
142                return duration;
143        }
144
145        private List<String> asCsv(Map<String, Long> map) throws Exception {
146                List<String> lines = newArrayList();
147                Joiner csv = Joiner.on(',');
148                List<String> sorted = natural().sortedCopy(map.keySet());
149                for (String key : sorted) {
150                        long timestamp = parseLong(key);
151                        long millis = map.get(key);
152                        long hours = millis / msPerHour;
153                        String line = csv.join(sdf.format(timestamp), hours + "");
154                        lines.add(line);
155                }
156                return lines;
157        }
158
159        private SimpleDateFormat getSimpleDateFormat(String dateFormat, String timezone) {
160                SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
161                sdf.setTimeZone(getTimeZone(timezone));
162                return sdf;
163        }
164
165        private long getMonth(long timestamp) throws Exception {
166                return sdf.parse(sdf.format(new Date(timestamp))).getTime();
167        }
168
169        private Map<String, Integer> asMap(List<JobExecution> builds) throws Exception {
170                Map<String, Integer> map = newHashMap();
171                for (JobExecution build : builds) {
172                        long startTime = build.getStartTime();
173                        long duration = build.getDuration();
174                        int minutes = (int) ceil((duration * 1D) / msPerMinute);
175                        for (int i = 0; i < minutes; i++) {
176                                long timestamp = startTime + (msPerMinute * i);
177                                long minute = timestamp / msPerMinute;
178                                String key = minute + "";
179                                Integer count = map.get(key);
180                                map.put(key, (count == null) ? 1 : (count + 1));
181                        }
182                }
183                return map;
184        }
185
186        private Map<String, Long> asMonthlyBuildHours(List<JobExecution> builds) throws Exception {
187                Map<String, Long> map = newHashMap();
188                for (JobExecution build : builds) {
189                        long startTime = build.getStartTime();
190                        long startMonth = getMonth(startTime);
191                        String key = startMonth + "";
192                        Long millis = map.get(key);
193                        map.put(key, (millis == null) ? 0L : (millis + build.getDuration()));
194                }
195                return map;
196        }
197
198        private List<JobExecution> getBuilds(List<Jenkins> jenkins) {
199                List<JobExecution> builds = newArrayList();
200                for (Jenkins server : jenkins) {
201                        for (Job job : server.getJobs()) {
202                                builds.addAll(job.getExecutions());
203                        }
204                }
205                return builds;
206        }
207
208        @Test
209        @Ignore
210        public void test2() {
211                try {
212                        Stopwatch sw = createStarted();
213                        List<String> keys = getKeys(stacks);
214                        List<Jenkins> jenkins = getJenkins(keys);
215                        summarize(jenkins);
216                        info("%s jenkins servers - %s", jenkins.size(), getTime(sw));
217                        for (TypeAndTeam tt : jobsByTypeAndTeam.keySet()) {
218                                List<Job> jobs = jobsByTypeAndTeam.get(tt);
219                                long builds = getTotalExecutions(jobs);
220                                SummaryStatistics ss = getCalculatedDurationStatistics(jobs);
221                                String duration = getTime(new Double(ss.getSum()).longValue());
222                                info("%s:%s  %s %s %s", tt.getTeam(), tt.getType(), jobs.size(), builds, duration);
223                                if (tt.getTeam() == Team.STUDENT && tt.getType() == Type.TEST) {
224                                        summarizeJobs(jobs);
225                                }
226                                if (tt.getTeam() == Team.STUDENT && tt.getType() == Type.OTHER) {
227                                        summarizeJobs(jobs);
228                                }
229                                if (tt.getTeam() == Team.RICE && tt.getType() == Type.TEST) {
230                                        summarizeJobs(jobs);
231                                }
232                        }
233                } catch (Throwable e) {
234                        e.printStackTrace();
235                }
236        }
237
238        protected static void summarizeJobs(List<Job> jobs) {
239                List<Job> sorted = newArrayList(jobs);
240                sort(sorted, JobTimeComparator.INSTANCE);
241                reverse(sorted);
242                List<String> columns = ImmutableList.of("name", "builds", "total time");
243                List<Object[]> rows = newArrayList();
244                for (Job job : sorted) {
245                        long builds = getLastBuildNumber(job);
246                        long time = new Double(getCalculatedDurationStatistics(job).getSummary().getSum()).longValue();
247                        Object[] row = { job.getName(), builds, getTime(time) };
248                        rows.add(row);
249                }
250                logTable(columns, rows);
251        }
252
253        protected static void summarize(List<Jenkins> jenkins) {
254                List<String> columns = ImmutableList.of("host", "jobs", "recorded builds", "total builds", "duration recorded", "total duration", "first job", "last job", "elapsed");
255                List<Object[]> rows = newArrayList();
256                for (Jenkins element : jenkins) {
257                        rows.add(summarize(element));
258                }
259                logTable(columns, rows);
260        }
261
262        protected static Object[] summarize(Jenkins jenkins) {
263                int executions = 0;
264                int totalBuilds = 0;
265                long recordedDuration = 0;
266                long totalDuration = 0;
267
268                SummaryStatistics startTimes = getStartTimeStats(jenkins.getJobs());
269                for (Job job : jenkins.getJobs()) {
270                        updateTypeMap(job);
271                        updateTeamMap(job);
272                        updateTypeAndTeamMap(job);
273                        executions += job.getExecutions().size();
274                        int lastBuildNumber = getLastBuildNumber(job);
275                        recordedDuration += new Double(getRecordedDurationStatistics(job).getSummary().getSum()).longValue();
276                        totalDuration += new Double(getCalculatedDurationStatistics(job).getSummary().getSum()).longValue();
277                        totalBuilds += lastBuildNumber;
278                }
279                long firstStartTime = new Double(startTimes.getMin()).longValue();
280                long lastStartTime = new Double(startTimes.getMax()).longValue();
281                String jobs = FormatUtils.getCount(jenkins.getJobs().size());
282                String execs = FormatUtils.getCount(executions);
283                String builds = FormatUtils.getCount(totalBuilds);
284                String durationRecorded = FormatUtils.getTime(recordedDuration);
285                String durationTotal = FormatUtils.getTime(totalDuration);
286                String firstJob = FormatUtils.getDate(firstStartTime);
287                String lastJob = FormatUtils.getDate(lastStartTime);
288                String elapsed = FormatUtils.getTime(lastStartTime - firstStartTime);
289                return new Object[] { jenkins.getHostname(), jobs, execs, builds, durationRecorded, durationTotal, firstJob, lastJob, elapsed };
290        }
291
292        protected static void updateTypeAndTeamMap(Job job) {
293                Type type = getType(job);
294                Team team = getTeam(job);
295                TypeAndTeam tt = TypeAndTeam.builder().withTeam(team).withType(type).build();
296                List<Job> jobs = jobsByTypeAndTeam.get(tt);
297                if (jobs == null) {
298                        jobs = newArrayList();
299                        jobsByTypeAndTeam.put(tt, jobs);
300                }
301                jobs.add(job);
302        }
303
304        protected static void updateTypeMap(Job job) {
305                Type type = getType(job);
306                List<Job> jobs = jobsByType.get(type);
307                if (jobs == null) {
308                        jobs = newArrayList();
309                        jobsByType.put(type, jobs);
310                }
311                jobs.add(job);
312        }
313
314        protected static void updateTeamMap(Job job) {
315                Team team = getTeam(job);
316                List<Job> jobs = jobsByTeam.get(team);
317                if (jobs == null) {
318                        jobs = newArrayList();
319                        jobsByTeam.put(team, jobs);
320                }
321                jobs.add(job);
322        }
323
324        protected List<Jenkins> getJenkins(List<String> keys) {
325                List<Jenkins> list = newArrayList();
326                for (String key : keys) {
327                        String string = getJson(key);
328                        info("parsing -> json");
329                        Jenkins element = json.readString(string, Jenkins.class);
330                        list.add(element);
331                }
332                return ImmutableList.copyOf(list);
333        }
334
335        protected String getJson(String key) {
336                try {
337                        String url = format("http://%s/%s", BUCKET, key);
338                        File file = getFile(url);
339                        if (file.exists()) {
340                                info("loading -> [%s]", file);
341                                return readFileToString(file, UTF8);
342                        } else {
343                                info("loading -> [%s]", url);
344                                String content = LocationUtils.toString(url, UTF8);
345                                info("creating -> [%s]", file);
346                                writeStringToFile(file, content);
347                                return content;
348                        }
349                } catch (IOException e) {
350                        throw illegalState(e);
351                }
352        }
353
354        protected File getFile(String url) {
355                ResourceLoader loader = new DefaultResourceLoader();
356                Resource resource = loader.getResource(url);
357                String filename = resource.getFilename();
358                return new CanonicalFile("./target/jenkins/" + filename);
359        }
360
361        protected List<String> getKeys(List<String> stacks) {
362                List<String> keys = newArrayList();
363                for (String stack : stacks) {
364                        String key = getKey(REPO, stack, "latest");
365                        keys.add(key);
366                }
367                return ImmutableList.copyOf(keys);
368        }
369
370        protected static void info(String msg, Object... args) {
371                logger.info((args == null) ? msg : format(msg, args));
372        }
373
374        protected static Type getType(Job job) {
375                String name = job.getName();
376                if (name.contains("compile") || name.contains("unit") || name.contains("publish") || name.contains("site") || name.contains("license") || name.contains("nightly")
377                                || name.contains("third-party")) {
378                        return Type.BUILD;
379                }
380                if (name.contains("-ft") || name.contains("integration-test") || name.contains("test-integration") || name.contains("load-test") || name.contains("smoke-test")
381                                || name.contains("test-functional")) {
382                        return Type.TEST;
383                }
384                if (name.contains("deploy") || name.contains("-db-") || name.contains("impex")) {
385                        return Type.DEPLOY;
386                }
387                if (name.contains("release")) {
388                        return Type.RELEASE;
389                }
390                return Type.OTHER;
391        }
392
393        protected static Team getTeam(Job job) {
394                String name = job.getName();
395                if (name.contains("ks-")) {
396                        return Team.STUDENT;
397                }
398                if (name.contains("-ks")) {
399                        return Team.STUDENT;
400                }
401                if (name.contains("student")) {
402                        return Team.STUDENT;
403                }
404                if (name.contains("rice")) {
405                        return Team.RICE;
406                }
407                if (name.contains("rice")) {
408                        return Team.RICE;
409                }
410                if (name.contains("freemarker")) {
411                        return Team.RICE;
412                }
413                if (name.contains("eghm")) {
414                        return Team.RICE;
415                }
416                if (name.contains("commons-beanutils")) {
417                        return Team.RICE;
418                }
419                if (name.contains("ole")) {
420                        return Team.OLE;
421                }
422                if (name.contains("OLE")) {
423                        return Team.OLE;
424                }
425                if (name.contains("mobility")) {
426                        return Team.MOBILITY;
427                }
428                if (name.contains("kpme")) {
429                        return Team.KHR;
430                }
431                if (name.contains("khr")) {
432                        return Team.KHR;
433                }
434                if (name.contains("kdo")) {
435                        return Team.DEVOPS;
436                }
437                if (name.contains("maven-")) {
438                        return Team.DEVOPS;
439                }
440                if (name.contains("update-ci")) {
441                        return Team.DEVOPS;
442                }
443                if (name.contains("kuali-")) {
444                        return Team.DEVOPS;
445                }
446                if (name.contains("ec2-")) {
447                        return Team.DEVOPS;
448                }
449                if (name.contains("devops")) {
450                        return Team.DEVOPS;
451                }
452                if (name.contains("restart-jenkins")) {
453                        return Team.DEVOPS;
454                }
455                if (name.contains("db-ojb")) {
456                        return Team.SHARED;
457                }
458                if (name.contains("impex")) {
459                        return Team.SHARED;
460                }
461                return Team.OTHER;
462        }
463
464}