Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(178)

Side by Side Diff: tools/gardening/lib/src/_luci_api.dart

Issue 3005443002: Additional tools for gardening. (Closed)
Patch Set: Moved files from gardening_tools to gardening and incorporated changes from johnniwinther Created 3 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 import 'dart:io';
Johnni Winther 2017/08/28 07:14:03 Remove this file.
2 import 'dart:async';
3 import 'dart:convert';
4 import 'package:base_lib/base_lib.dart';
5 import 'package:html/parser.dart' show parse;
6 import 'package:html/dom.dart';
7
8 const String LUCI_HOST = "luci-milo.appspot.com";
9 const String BUILD_CHROMIUM_HOST = "build.chromium.org";
10
11 /// Base class for communicating with Luci @ LogDog
12 /// Since some results can take a long time to get
13 /// and some results do not update that frequently,
14 /// [LuciApi] requires a cache to be used. If no
15 /// cache is needed, the [noCache] can be used.
16 class LuciApi {
17 final HttpClient _client = new HttpClient();
18
19 LuciApi();
20
21 /// [getBuildBots] fetches all build bots from luci.
22 /// The format is:
23 /// <li>
24 /// <a href="/buildbot/client.crashpad/crashpad_win_x86_wow64_rel">crashpa d_win_x86_wow64_rel</a>
25 /// </li>
26 /// <h3> client.dart </h3>
27 /// <li>
28 /// <a href="/buildbot/client.dart/analyze-linux-be">analyze-linux-be</a>
29 /// </li>
30 /// <li>
31 /// <a href="/buildbot/client.dart/analyze-linux-dev">analyze-linux-dev</a >
32 /// </li>
33 /// <li>
34 /// <a href="/buildbot/client.dart/analyze-linux-stable">analyze-linux-sta ble</a>
35 /// </li>
36 /// <li>
37 /// <a href="/buildbot/client.dart/analyzer-linux-release-be">analyzer-lin ux-release-be</a>
38 /// </li>
39 ///
40 /// We look for the section header matching clients, then
41 /// if we are in the right section, we take the <li> element
42 /// and transform to a build bot
43 ///
44 Future<Try<List<LuciBuildBot>>> getAllBuildBots(
45 String client, WithCache withCache) async {
46 var reqResult = await tryStartAsync(() => withCache(
47 () => _makeGetRequest(
48 new Uri(scheme: 'https', host: LUCI_HOST, path: "/")),
49 "all_buildbots"));
50 return reqResult.bind(parse).bind((htmlDoc) {
51 var takeSection = false; // this is really dirty, but the structure of
52 // the document is not really suited for anything else
53 return htmlDoc.body.children.where((node) {
54 if (node.localName == "li") return takeSection;
55 if (node.localName != "h3") {
56 takeSection = false;
57 return false;
58 }
59 // current node is <h3>
60 takeSection = client == node.text.trim();
61 return false;
62 });
63 }).bind((elements) {
64 // here we hold an iterable of buildbot elements
65 // <li>
66 // <a href="/buildbot/client.dart/analyzer-linux-release-be">analyzer- linux-release-be</a>
67 // </li>
68 return elements.map((element) {
69 var name = element.children[0].text;
70 var url = element.children[0].attributes['href'];
71 return new LuciBuildBot(client, name, url);
72 }).toList();
73 });
74 }
75
76 /// [getPrimaryBuilders] fetches all primary builders (the ones usually used b y gardeners)
77 /// by just stopping when reaching the first -dev buildbot
78 Future<Try<List<LuciBuildBot>>> getPrimaryBuilders(
79 String client, WithCache withCache) async {
80 var reqResult = await tryStartAsync(() => withCache(
81 () => _makeGetRequest(new Uri(
82 scheme: 'https',
83 host: BUILD_CHROMIUM_HOST,
84 path: "/p/$client/builders")),
85 "primary_buildbots"));
86 return reqResult.bind(parse).bind((Document htmlDoc) {
87 // We take all <table><tbody><tr>
88 var mainTableRows =
89 htmlDoc.body.getElementsByTagName("table")[0].children[0].children;
90 // pick until we reach a dev builder bot
91 return mainTableRows.takeWhile((Element el) {
92 return !el.children[0].children[0].text.contains("-dev");
93 }).map((Element el) {
94 return new LuciBuildBot(client, el.children[0].children[0].text,
95 el.children[0].children[0].attributes["href"]);
96 });
97 });
98 }
99
100 /// Generates a suitable url to fetch information about a buildbot.
101 /// The field [amount] is the number of recent builds to fetch
102 Future<Try<LuciBuildBotDetail>> getBuildBotDetails(
103 String client, String botName, WithCache withCache,
104 [int amount = 25]) async {
105 var uri = new Uri(
106 scheme: "https",
107 host: LUCI_HOST,
108 path: "/buildbot/$client/$botName/",
109 query: "limit=$amount");
110 var reqResult = await tryStartAsync(
111 () => withCache(() => _makeGetRequest(uri), uri.path.toString()));
112 return reqResult.bind(parse).bind((Document document) {
113 var detail = new LuciBuildBotDetail()
114 ..client = client
115 ..botName = botName
116 ..currentBuild = getCurrentBuild(document)
117 ..latestBuilds = getBuildOverviews(document);
118 return detail;
119 });
120 }
121
122 /// Get the [BuildDetail] information for the requested build.
123 /// The url requested is on the form /buildbot/$client/$botName/$buildNumber.
124 Future<Try<BuildDetail>> getBuildDetails(String client, String botName,
125 int buildNumber, WithCache withCache) async {
126 var uri = new Uri(
127 scheme: "https",
128 host: LUCI_HOST,
129 path: "/buildbot/$client/$botName/$buildNumber");
130 var reqResult = await tryStartAsync(
131 () => withCache(() => _makeGetRequest(uri), uri.path.toString()));
132 return reqResult.bind(parse).bind((Document document) {
133 return new BuildDetail()
134 ..client = client
135 ..botName = botName
136 ..buildNumber = buildNumber
137 ..results = getBuildResults(document)
138 ..steps = getBuildSteps(document)
139 ..buildProperties = getBuildProperties(document)
140 ..blameList = getBuildBlameList(document)
141 ..timing = getBuildTiming(document)
142 ..allChanges = getBuildGitCommits(document);
143 });
144 }
145
146 Future<String> _makeGetRequest(Uri uri) async {
147 var request = await _client.getUrl(uri);
148 var response = await request.close();
149
150 if (response.statusCode != 200) {
151 response.drain();
152 throw new HttpException(response.reasonPhrase, uri: uri);
153 }
154
155 return response.transform(UTF8.decoder).join();
156 }
157
158 void close() {
159 _client.close();
160 }
161 }
162
163 // Functions to generate objects from the HTML representation
164
165 CurrentBuild getCurrentBuild(Document document) {
166 var currentBuildColumn = document.body.getElementsByClassName("column")[1];
167 // <h2>Current Builds (1):</h2> <------ we are here
168 // <ul>
169 // <li>
170 // <a href="1277">#1277</a>
171 if (currentBuildColumn.children.length > 1) {
172 var li = currentBuildColumn.children[1].children[0];
173 return new CurrentBuild()
174 ..buildNumber = int.parse(li.children[0].attributes["href"])
175 ..duration = li.text
176 .substring(li.text.indexOf("[") + 1, li.text.indexOf("]"))
177 .trim();
178 }
179 return null;
180 }
181
182 List<BuildOverview> getBuildOverviews(Document document) {
183 var main = document.body.getElementsByClassName("main").first;
184 var tables = main.getElementsByClassName("info");
185 if (tables.length == 0) {
186 // happens when there is no builds at all
187 return new List<BuildOverview>();
188 }
189 // We want to iterate over all table rows
190 // <table> <tbody> <tr>
191 return tables[0]
192 .children[0]
193 .children
194 .skip(1)
195 .map(getBuildOverviewFromTableRow)
196 .toList();
197 }
198
199 BuildOverview getBuildOverviewFromTableRow(Element tr) {
200 var bo = new BuildOverview()
201 ..time = tr.children[0].firstChild.text
202 ..mainRevision = tr.children[1].text
203 ..result = tr.children[2].text
204 ..buildNumber = int.parse(tr.children[3].text.substring(1));
205 var changesText = tr.children[4].text;
206 bo.hasMultipleChanges =
207 changesText.contains(',') || changesText.contains("changes");
208 bo.info = tr.children[5].innerHtml
209 .split("<br>")
210 .map((info) => info.trim())
211 .toList();
212
213 return bo;
214 }
215
216 String getBuildResults(Document document) {
217 // <html> <--- we are here
218 // ...
219 // <div class="column">
220 // <h2>Results:</h2>
221 // <p class="success result">Build Successful
222 return document.getElementsByClassName("column")[0].children[1].text.trim();
223 }
224
225 List<Step> getBuildSteps(Document document) {
226 // <html> <--- we are here
227 // ...
228 // <ol id="steps" class="standard">
229 // <li class="verbosity-Normal">
230 return document
231 .getElementsByClassName("standard")
232 .first
233 .children
234 .map(getBuildStep)
235 .toList();
236 }
237
238 Step getBuildStep(Element li) {
239 // <li class="verbosity-Normal"> <--- we are here
240 // <div class="status-Success result">
241 // <span class="duration"
242 // data-starttime="2017-08-21T17:01:41Z"
243 // data-endtime="2017-08-21T19:40:20Z">
244 // ( 2 hrs 38 mins )</span>
245 // <b>steps</b>
246 // <span>
247 // <div class="step-text">running steps via annotated script</div>
248 // </span>
249 // </div>
250 // <ul>
251 // <li class="sublink">
252 var divResult = li.children[0];
253 return new Step(
254 divResult.children[1].text,
255 divResult.getElementsByClassName("step-text").first.text.trim(),
256 divResult.className.replaceAll("status-", "").replaceAll(" result", ""),
257 divResult.children.first.text.trim())
258 ..subLinks = li
259 .getElementsByClassName("sublink")
260 .map((liSub) => new SubLink(
261 liSub.firstChild.text, liSub.firstChild.attributes["href"]))
262 .toList();
263 }
264
265 List<BuildProperty> getBuildProperties(Document document) {
266 // <html> <-- we are here
267 // ...
268 // <div class="column">
269 // ...
270 // <div class="column">
271 // <h2>Build Properties:</h2>
272 // <table class="info BuildProperties" width="100%">
273 // <tbody>
274 // <tr>
275 // <th>Name</th>
276 // <th>Value</th>
277 // <th>Source</th>
278 // </tr>
279 var buildTable = document.getElementsByClassName("column")[1].children[1];
280 return buildTable.children.first.children.skip(1).map((tr) {
281 // here we hold a <tr> with cells of information
282 return new BuildProperty(tr.children[0].text.trim(),
283 tr.children[1].text.trim(), tr.children[2].text.trim());
284 }).toList();
285 }
286
287 List<String> getBuildBlameList(Document document) {
288 // <html> <-- we are here
289 // ...
290 // <div class="column">
291 // ...
292 // <div class="column">
293 // <h2>Build Properties:</h2>
294 // <table class="info BuildProperties" width="100%">
295 // <h2>Blamelist:</h2>
296 // <ol>
297 // <li>
298 var blameList = document.getElementsByClassName("column")[1].children[3];
299 return blameList.children.map((li) {
300 return li.text.replaceAll("ohnoyoudont", "").trim();
301 }).toList();
302 }
303
304 Timing getBuildTiming(Document document) {
305 // <html> <-- we are here
306 // ...
307 // <div class="column">
308 // ...
309 // <div class="column">
310 // <h2>Build Properties:</h2>
311 // <table class="info BuildProperties" width="100%">
312 // <h2>Blamelist:</h2>
313 // <ol> ...
314 // <h2>Timing:</h2>
315 // <table..
316 var timingTableBody =
317 document.getElementsByClassName("column")[1].children[5].children.first;
318 var infos = timingTableBody.children
319 .map((tr) => tr.children.last.text.trim())
320 .toList();
321 return new Timing(infos[0], infos[1], infos[2]);
322 }
323
324 List<GitCommit> getBuildGitCommits(Document document) {
325 // <html> <-- we are here
326 // ...
327 // <div class="column">
328 // ...
329 // <div class="column">
330 // ...
331 // <div class="column">
332 // <h2>All Changes:</h2>
333 // <ol>
334 // <li>
335 var olChanges = document.getElementsByClassName("column")[2].children[1];
336 return olChanges.children.map(getBuildGitCommit).toList();
337 }
338
339 GitCommit getBuildGitCommit(Element li) {
340 // <li> <--- we are here
341 // <h3>Deprecate MethodElement.getReifiedType</h3>
342 // <table class="info">
343 // <tbody>
344 // <tr>
345 String title = li.children[0].text;
346 Element revisionAnchor =
347 li.children[1].children[0].children.last.children[1].firstChild;
348 String commitUrl = revisionAnchor.attributes["href"];
349 String revision = revisionAnchor.text;
350 String changedBy = li.children[1].children[0].children.first.children[1].text
351 .replaceAll("ohnoyoudont", "")
352 .trim();
353 String comments = li.getElementsByClassName("comments").first.text;
354 List<String> files =
355 li.getElementsByClassName("file").map((liFile) => liFile.text).toList();
356
357 return new GitCommit(title, revision, commitUrl, changedBy, comments)
358 ..changedFiles = files;
359 }
360
361 // Structured classes to relay information scraped from the luci bot web pages
362
363 /// [LuciBuildBot] holds information about a build bot
364 class LuciBuildBot {
365 String client;
366 String name;
367 String url;
368
369 LuciBuildBot(this.client, this.name, this.url);
370
371 @override
372 String toString() {
373 return "LuciBuildBot { client: $client, name: $name, url: $url }";
374 }
375 }
376
377 /// [LuciBuildBotDetail] holds information about a bots current build,
378 /// all latest builds (excluding the current)
379 class LuciBuildBotDetail {
380 String client;
381 String botName;
382 CurrentBuild currentBuild;
383 List<BuildOverview> latestBuilds;
384
385 int latestBuildNumber() {
386 if (currentBuild == null && latestBuilds.length == 0) {
387 return -1;
388 }
389 return currentBuild != null
390 ? currentBuild.buildNumber
391 : latestBuilds[0].buildNumber;
392 }
393
394 @override
395 String toString() {
396 StringBuffer buffer = new StringBuffer();
397 buffer.writeln(currentBuild == null
398 ? "Current build: none\n"
399 : "Current build: ${currentBuild.buildNumber} (${currentBuild.duration}) \n");
400
401 buffer.writeln(
402 "time\t\t\t\t\trevision\t\t\t\t\tresult\tbuild #\tmult. rev.\tinfo");
403
404 latestBuilds.forEach((build) {
405 if (build.time.length > 31) {
406 buffer.writeln(
407 "${build.time}\t${build.mainRevision}\t${build.result}\t${build.buil dNumber}\t${build.hasMultipleChanges}\t\t${build.info.join(',')}");
408 } else {
409 buffer.writeln(
410 "${build.time}\t\t${build.mainRevision}\t${build.result}\t${build.bu ildNumber}\t${build.hasMultipleChanges}\t\t${build.info.join(',')}");
411 }
412 });
413
414 return buffer.toString();
415 }
416 }
417
418 /// [CurrentBuild] shows current build informaiton
419 class CurrentBuild {
420 int buildNumber;
421 String duration;
422 }
423
424 /// [BuildOverview] has overview information about a build, such as time, mainRe vision and result
425 class BuildOverview {
426 String time;
427 String mainRevision;
428 String result;
429 int buildNumber;
430 bool hasMultipleChanges;
431 List<String> info;
432 }
433
434 class BuildDetail {
435 String client;
436 String botName;
437 int buildNumber;
438 String results;
439 List<Step> steps;
440 List<BuildProperty> buildProperties;
441 List<String> blameList;
442 Timing timing;
443 List<GitCommit> allChanges;
444
445 @override
446 String toString() {
447 StringBuffer buffer = new StringBuffer();
448 buffer.writeln("--------------------------------------");
449 buffer.writeln(results);
450 buffer.writeln(timing);
451 buffer.writeln("----------------STEPS-----------------");
452 if (steps != null) steps.forEach(buffer.writeln);
453 buffer.writeln("----------BUILD PROPERTIES------------");
454 if (buildProperties != null) buildProperties.forEach(buffer.writeln);
455 buffer.writeln("-------------BLAME LIST---------------");
456 if (blameList != null) blameList.forEach(buffer.writeln);
457 buffer.writeln("------------ALL CHANGES---------------");
458 if (allChanges != null) allChanges.forEach(buffer.writeln);
459 return buffer.toString();
460 }
461 }
462
463 class Step {
464 String name;
465 String description;
466 String result;
467 String time;
468 List<SubLink> subLinks;
469 Step(this.name, this.description, this.result, this.time);
470
471 @override
472 String toString() {
473 StringBuffer buffer = new StringBuffer();
474 buffer.writeln("$result: $name - $description ($time)");
475 subLinks.forEach((subLink) {
476 buffer.writeln("\t${subLink}");
477 });
478 return buffer.toString();
479 }
480 }
481
482 class SubLink {
483 String name;
484 String url;
485 SubLink(this.name, this.url);
486
487 @override
488 String toString() {
489 return "$name | $url";
490 }
491 }
492
493 class BuildProperty {
494 String name;
495 String value;
496 String source;
497 BuildProperty(this.name, this.value, this.source);
498
499 @override
500 String toString() {
501 return "$name\t$value\t$source";
502 }
503 }
504
505 class Timing {
506 String start;
507 String end;
508 String elapsed;
509 Timing(this.start, this.end, this.elapsed);
510
511 @override
512 String toString() {
513 return "start: $start\tend: $end\telapsed: $elapsed";
514 }
515 }
516
517 class GitCommit {
518 String title;
519 String revision;
520 String commitUrl;
521 String changedBy;
522 String comments;
523 List<String> changedFiles;
524 GitCommit(
525 this.title, this.revision, this.commitUrl, this.changedBy, this.comments);
526
527 @override
528 String toString() {
529 StringBuffer buffer = new StringBuffer();
530
531 buffer.writeln(title);
532 buffer.writeln("revision: $revision");
533 buffer.writeln("commitUrl: $commitUrl");
534 buffer.writeln("changedBy: $changedBy");
535 buffer.write("\n");
536 buffer.writeln(comments);
537 buffer.write("\nfiles:\n");
538 changedFiles.forEach(buffer.writeln);
539 return buffer.toString();
540 }
541 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698