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

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 Add copyright header (for all .dart files): // Co
2 import 'dart:async';
3 import 'dart:convert';
4 import 'package:http/http.dart' as http;
5 import 'package:html/parser.dart' show parse;
6 import 'try.dart';
7 import 'cache_new.dart';
8
9 const String LUCI_HOST = "luci-milo.appspot.com";
10 // const String BUILD_CHROMIUM_HOST = "build.chromium.org";
Johnni Winther 2017/08/28 07:14:03 Remove?
11
12 typedef void ModifyRequestFunction(HttpClientRequest request);
13
14 /// Base class for communicating with Milo @ LogDog
15 /// Some information is found through the api
16 /// <https://docs.google.com/document/d/1HbPp7Sy7ofC
17 /// U7C9USqcE91VubGg_IIET2GUj9iknev4/edit#>
18 /// and some information is found via screen-scraping.
19 class LuciApi {
Johnni Winther 2017/08/28 07:14:04 Just call this [Luci].
20 final HttpClient _client = new HttpClient();
21
22 LuciApi();
23
24 /// [getBuildBots] fetches all build bots from luci (we cannot
25 /// get this from the api). The format is:
26 /// <li>
27 /// <a href="/buildbot/client.crashpad/crashpad_win_x86_wow64_rel">
28 /// crashpad_win_x86_wow64_rel</a>
29 /// </li>
30 /// <h3> client.dart </h3>
31 /// <li>
32 /// <a href="/buildbot/client.dart/analyze-linux-be">analyze-linux-be</a>
33 /// </li>
34 /// <li>
35 /// <a href="/buildbot/client.dart/analyze-linux-dev">analyze-linux-dev</a >
Johnni Winther 2017/08/28 07:14:04 Long line.
36 /// </li>
37 /// <li>
38 /// <a href="/buildbot/client.dart/analyze-linux-stable">
39 /// analyze-linux-stable</a>
40 /// </li>
41 /// <li>
42 /// <a href="/buildbot/client.dart/analyzer-linux-release-be">
43 /// analyzer-linux-release-be</a>
44 /// </li>
45 ///
46 /// We look for the section header matching clients, then
47 /// if we are in the right section, we take the <li> element
48 /// and transform to a build bot
49 ///
50 Future<Try<List<LuciBuildBot>>> getAllBuildBots(
51 String client, WithCacheFunction withCache) async {
52 return await tryStartAsync(() => withCache(
53 () => _makeGetRequest(
54 new Uri(scheme: 'https', host: LUCI_HOST, path: "/")),
55 "all_buildbots"))
56 .then((Try<String> tryRes) => tryRes.bind(parse).bind((htmlDoc) {
57 var takeSection =
58 false; // this is really dirty, but the structure of
Johnni Winther 2017/08/28 07:14:04 Move the comment about the variable declaration (d
59 // the document is not really suited for anything else
60 return htmlDoc.body.children.where((node) {
61 if (node.localName == "li") return takeSection;
62 if (node.localName != "h3") {
63 takeSection = false;
64 return false;
65 }
66 // current node is <h3>
67 takeSection = client == node.text.trim();
68 return false;
69 });
70 }).bind((elements) {
71 // here we hold an iterable of buildbot elements
72 // <li>
73 // <a href="/buildbot/client.dart/analyzer-linux-release-be">
74 // analyzer-linux-release-be</a>
75 // </li>
76 return elements.map((element) {
77 var name = element.children[0].text;
78 var url = element.children[0].attributes['href'];
79 return new LuciBuildBot(client, name, url);
80 }).toList();
81 }));
82 }
83
84 /// [getPrimaryBuilders] fetches all primary builders
85 /// (the ones usually used by gardeners) by not including buildbots with
86 /// the name -dev, -stable or -integration.
87 Future<Try<List<LuciBuildBot>>> getPrimaryBuilders(
88 String client, WithCacheFunction withCache) async {
89 return await getAllBuildBots(client, withCache)
90 .then((Try<List<LuciBuildBot>> tryRes) {
91 return tryRes
92 .bind((buildBots) => buildBots.where((LuciBuildBot buildBot) {
93 return !(buildBot.name.contains("-dev") ||
94 buildBot.name.contains("-stable") ||
95 buildBot.name.contains("-integration"));
96 }));
97 });
98 }
99
100 /// Calling the Milo Api to get latest builds for this bot,
101 /// where the field [amount] is the number of recent builds to fetch
Johnni Winther 2017/08/28 07:14:04 Add period to end the sentence.
102 Future<Try<List<BuildDetail>>> getBuildBotDetails(
103 String client, String botName, WithCacheFunction withCache,
104 [int amount = 20]) async {
105 var uri = new Uri(
106 scheme: "https",
107 host: LUCI_HOST,
108 path: "prpc/milo.Buildbot/GetBuildbotBuildsJSON");
109 var body = {
110 "master": client,
111 "builder": botName,
112 "limit": amount,
113 "includeCurrent": true
114 };
115 var result = await tryStartAsync(() => withCache(
116 () => _makePostRequest(uri, JSON.encode(body), {
117 HttpHeaders.CONTENT_TYPE: "application/json",
118 HttpHeaders.ACCEPT: "application/json"
119 }),
120 '${uri.path.toString()}_${botName}_$amount'));
Johnni Winther 2017/08/28 07:14:04 Just ${uri.path} since [path] is already a string.
121 return result.bind(JSON.decode).bind((json) {
122 return json["builds"].map((b) {
123 var build = JSON.decode(UTF8.decode(BASE64.decode(b["data"])));
124 return getBuildDetailFromJson(client, botName, build);
125 }).toList();
126 });
127 }
128
129 /// Calling the Milo Api to get latest builds for this bot,
130 /// where the field [amount] is the number of recent builds to fetch
Johnni Winther 2017/08/28 07:14:03 Add period to end the sentence.
131 Future<Try<BuildDetail>> getBuildBotBuildDetails(String client,
132 String botName, int buildNumber, WithCacheFunction withCache) async {
133 var uri = new Uri(
134 scheme: "https",
135 host: LUCI_HOST,
136 path: "prpc/milo.Buildbot/GetBuildbotBuildJSON");
137 var body = {"master": client, "builder": botName, "buildNum": buildNumber};
138 print(body);
139 var result = await tryStartAsync(() => withCache(
140 () => _makePostRequest(uri, JSON.encode(body), {
141 HttpHeaders.CONTENT_TYPE: "application/json",
142 HttpHeaders.ACCEPT: "application/json"
143 }),
144 '${uri.path.toString()}_${botName}_$buildNumber'));
145 return result.bind(JSON.decode).bind((json) {
146 var build = JSON.decode(UTF8.decode(BASE64.decode(json["data"])));
147 return getBuildDetailFromJson(client, botName, build);
148 });
149 }
150
151 Future<String> _makeGetRequest(Uri uri) async {
152 var request = await _client.getUrl(uri);
153 var response = await request.close();
154 if (response.statusCode != 200) {
155 response.drain();
156 throw new HttpException(response.reasonPhrase, uri: uri);
157 }
158 return response.transform(UTF8.decoder).join();
159 }
160
161 Future<String> _makePostRequest(
162 Uri uri, Object body, Map<String, String> headers) async {
163 var response = await http.post(uri, body: body, headers: headers);
164 if (response.statusCode != 200) {
165 throw new HttpException(response.reasonPhrase, uri: uri);
166 }
167 // Prpc outputs a prefix to combat vulnerability
Johnni Winther 2017/08/28 07:14:04 Add period to end the sentence.
168 if (response.body.startsWith(")]}'")) {
169 return response.body.substring(4);
170 }
171 return response.body;
172 }
173
174 void close() {
175 _client.close();
176 }
177 }
178
179 BuildDetail getBuildDetailFromJson(
Johnni Winther 2017/08/28 07:14:03 Add documentation.
180 String client, String botName, dynamic build) {
181 List<GitCommit> changes = build["sourceStamp"]["changes"].map((change) {
182 return new GitCommit(change["revision"], change["revLink"], change["who"],
183 change["comments"])
184 ..changedFiles = change["files"].map((file) => file["name"]);
185 }).toList();
186
187 List<BuildProperty> properties = build["properties"].map((prop) {
188 return new BuildProperty(prop[0], prop[1].toString(), prop[2]);
189 }).toList();
190
191 List<BuildStep> steps = build["steps"].map((step) {
192 var start =
193 new DateTime.fromMillisecondsSinceEpoch(step["times"][0] * 1000);
194 DateTime end = null;
195 if (step["times"][1] != null) {
196 end = new DateTime.fromMillisecondsSinceEpoch(step["times"][1] * 1000);
197 }
198 return new BuildStep(step["name"], step["text"], step["results"].toString(),
199 start, end, step["step_number"], step["isStarted"], step["isFinished"])
200 ..logs = step["logs"].map((log) => new BuildLog(log[0], log[1]));
201 }).toList();
202
203 DateTime end = null;
204 if (build["times"][1] != null) {
205 end = new DateTime.fromMillisecondsSinceEpoch(build["times"][1] * 1000);
206 }
207
208 Timing timing = new Timing(
209 new DateTime.fromMillisecondsSinceEpoch(build["times"][0] * 1000), end);
210
211 return new BuildDetail()
212 ..client = client
213 ..botName = botName
214 ..buildNumber = build["number"]
215 ..results = build["text"].join(' ')
216 ..finished = build["finished"]
217 ..blameList = build["blame"]
218 ..allChanges = changes
219 ..buildProperties = properties
220 ..steps = steps
221 ..timing = timing;
222 }
223
224 // Structured classes to relay information from api and web pages
225
226 /// [LuciBuildBot] holds information about a build bot
227 class LuciBuildBot {
228 String client;
229 String name;
230 String url;
Johnni Winther 2017/08/28 07:14:03 Make the fields final.
231
232 LuciBuildBot(this.client, this.name, this.url);
233
234 @override
235 String toString() {
236 return "LuciBuildBot { client: $client, name: $name, url: $url }";
237 }
238 }
239
240 class BuildDetail {
Johnni Winther 2017/08/28 07:14:03 Add document to the class and all its fields.
241 String client;
242 String botName;
243 int buildNumber;
244 String results;
245 bool finished;
246 List<BuildStep> steps;
247 List<BuildProperty> buildProperties;
248 List<String> blameList;
249 Timing timing;
250 List<GitCommit> allChanges;
Johnni Winther 2017/08/28 07:14:04 Add a constructor that takes these as argument and
251
252 @override
253 String toString() {
254 StringBuffer buffer = new StringBuffer();
255 buffer.writeln("--------------------------------------");
256 buffer.writeln(results);
257 buffer.writeln(timing);
258 buffer.writeln("----------------STEPS-----------------");
259 if (steps != null) steps.forEach(buffer.writeln);
260 buffer.writeln("----------BUILD PROPERTIES------------");
261 if (buildProperties != null) buildProperties.forEach(buffer.writeln);
262 buffer.writeln("-------------BLAME LIST---------------");
263 if (blameList != null) blameList.forEach(buffer.writeln);
264 buffer.writeln("------------ALL CHANGES---------------");
265 if (allChanges != null) allChanges.forEach(buffer.writeln);
266 return buffer.toString();
267 }
268 }
269
270 class BuildStep {
Johnni Winther 2017/08/28 07:14:03 Add document to the class and all its fields.
271 final String name;
272 final String description;
273 final String result;
274 final DateTime start;
275 final DateTime end;
276 final int number;
277 final bool isStarted;
278 final bool isFinished;
279 List<BuildLog> logs;
280 BuildStep(this.name, this.description, this.result, this.start, this.end,
Johnni Winther 2017/08/28 07:14:04 Add an empty line before the constructor.
281 this.number, this.isStarted, this.isFinished);
282
283 @override
284 String toString() {
285 StringBuffer buffer = new StringBuffer();
286 buffer.writeln(
287 "${result == '[0, []]' ? 'SUCCESS' : result}: $name - $description ($sta rt, $end)");
Johnni Winther 2017/08/28 07:14:03 Long line.
288 logs.forEach((subLink) {
289 buffer.writeln("\t${subLink}");
290 });
291 return buffer.toString();
292 }
293 }
294
295 class BuildLog {
Johnni Winther 2017/08/28 07:14:03 Add document to the class and its fields.
296 String name;
297 String url;
298 BuildLog(this.name, this.url);
299
300 @override
301 String toString() {
302 return "$name | $url";
303 }
304 }
305
306 class BuildProperty {
Johnni Winther 2017/08/28 07:14:03 Add document to the class and its fields.
307 String name;
308 String value;
309 String source;
310 BuildProperty(this.name, this.value, this.source);
Johnni Winther 2017/08/28 07:14:04 Add an empty line before the constructor.
311
312 @override
313 String toString() {
314 return "$name\t$value\t$source";
315 }
316 }
317
318 class Timing {
319 final DateTime start;
320 final DateTime end;
321 Timing(this.start, this.end);
322
323 @override
324 String toString() {
325 return "start: $start\tend: $end";
326 }
327 }
328
329 class GitCommit {
Johnni Winther 2017/08/28 07:14:03 Add document to the class and its fields.
330 final String revision;
331 final String commitUrl;
332 final String changedBy;
333 final String comments;
334 List<String> changedFiles;
335 GitCommit(this.revision, this.commitUrl, this.changedBy, this.comments);
Johnni Winther 2017/08/28 07:14:04 Add an empty line before the constructor.
336
337 @override
338 String toString() {
339 StringBuffer buffer = new StringBuffer();
340 buffer.writeln("revision: $revision");
341 buffer.writeln("commitUrl: $commitUrl");
342 buffer.writeln("changedBy: $changedBy");
343 buffer.write("\n");
344 buffer.writeln(comments);
345 buffer.write("\nfiles:\n");
346 changedFiles.forEach(buffer.writeln);
347 return buffer.toString();
348 }
349 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698