Skip to content

Commit f2f03f1

Browse files
thosakwemichaelhixson
authored andcommitted
Add Dart Angel Framework (#4013)
* create angel * Angel project setup * bin/main.dart * Spawn $(N_CORES) instances * Pass args to Angel entrypoint * Move newline in usage print * Created model files * Include serializers as parts * Connect to Mongo * Add JSON+plaintext * Fetch random world * Fortunes + remaining routes * Add mustache * Postgres * Run pub get instead of slower pub upgrade * Add missing URLS * Commit lockfile * Update world.dart to serialize as randomNumber instead of randomnumber * ignore .g.part * Remove .g.part * All tests run for Angel * Angel postgres passes * Remove development config * Remove logging config * Version information for Angel benchmarks * Add link to Dart benchmarks * Format Dart code * Remove logging usage in bin/main.dart * Remove Dart gen files, run them in Docker * ignore * angel
1 parent c8821e9 commit f2f03f1

18 files changed

+459
-0
lines changed

frameworks/Dart/angel/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
!bin/
2+
.dart_tool
3+
.packages
4+
*.g.part
5+
*.g.dart

frameworks/Dart/angel/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Angel Framework Benchmarking Test
2+
This test adds [Angel](https://angel-dart.github.io),
3+
a full-featured framework for Dart, to the
4+
[benchmarking test suite](https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/frameworks/Dart). The test is based on the Dart Benchmarking Test.
5+
6+
## Versions
7+
The `pubspec.lock` file is included; so that dependencies are kept consistent between deployments.
8+
The tests included in this benchmark are a demonstration of:
9+
* [Dart SDK version 2.0.0](http://www.dartlang.org/)
10+
* [Angel Framework version `^2.0.0-alpha`](https://pub.dartlang.org/packages/angel_framework/versions/2.0.0-alpha.1)
11+
12+
## Test URLs
13+
### JSON
14+
15+
http://localhost:8080/json
16+
17+
### PLAINTEXT
18+
19+
http://localhost:8080/plaintext
20+
21+
### DB
22+
23+
http://localhost:8080/db
24+
25+
### QUERY
26+
27+
http://localhost:8080/query?queries=
28+
29+
### CACHED QUERY
30+
31+
http://localhost:8080/cached_query?queries=
32+
33+
### UPDATE
34+
35+
http://localhost:8080/update?queries=
36+
37+
### FORTUNES
38+
39+
http://localhost:8080/fortunes
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM google/dart:2.0
2+
3+
COPY ./ ./
4+
5+
RUN pub get
6+
7+
RUN pub run build_runner build
8+
9+
CMD ANGEL_ENV=production dart bin/main.dart --type=postgres
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM google/dart:2.0
2+
3+
COPY ./ ./
4+
5+
RUN pub get
6+
7+
RUN pub run build_runner build
8+
9+
CMD ANGEL_ENV=production dart bin/main.dart --type=mongo
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"framework": "angel",
3+
"tests": [
4+
{
5+
"default": {
6+
"json_url": "/json",
7+
"plaintext_url": "/plaintext",
8+
"db_url": "/db",
9+
"query_url": "/queries/?queryCount=",
10+
"update_url": "/updates/?queryCount=",
11+
"fortune_url": "/fortunes",
12+
"port": 8080,
13+
"approach": "Realistic",
14+
"classification": "Fullstack",
15+
"database": "mongodb",
16+
"framework": "angel-mongo",
17+
"language": "Dart",
18+
"flavor": "None",
19+
"orm": "Raw",
20+
"platform": "None",
21+
"webserver": "None",
22+
"os": "Linux",
23+
"database_os": "Linux",
24+
"display_name": "Angel (MongoDB)",
25+
"notes": "",
26+
"versus": "dart"
27+
},
28+
"postgres": {
29+
"json_url": "/json",
30+
"plaintext_url": "/plaintext",
31+
"db_url": "/db",
32+
"query_url": "/queries/?queryCount=",
33+
"update_url": "/updates/?queryCount=",
34+
"fortune_url": "/fortunes",
35+
"port": 8080,
36+
"approach": "Realistic",
37+
"classification": "Fullstack",
38+
"database": "postgres",
39+
"framework": "angel-postgres",
40+
"language": "Dart",
41+
"flavor": "None",
42+
"orm": "Raw",
43+
"platform": "None",
44+
"webserver": "None",
45+
"os": "Linux",
46+
"database_os": "Linux",
47+
"display_name": "Angel (MongoDB)",
48+
"notes": "",
49+
"versus": "dart"
50+
}
51+
}
52+
]
53+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
import 'dart:isolate';
4+
import 'package:angel_framework/angel_framework.dart';
5+
import 'package:args/args.dart';
6+
import 'package:dart_angel_benchmark/dart_angel_benchmark.dart'
7+
as dart_angel_benchmark;
8+
9+
main(List<String> args) async {
10+
var argParser = ArgParser()
11+
..addOption('type',
12+
abbr: 't', allowed: ['mongo', 'postgres'], defaultsTo: 'mongo');
13+
14+
try {
15+
var argResults = argParser.parse(args);
16+
serverMain(StartConfig(0, argResults));
17+
18+
for (int i = 1; i < Platform.numberOfProcessors; i++) {
19+
var onError = new ReceivePort();
20+
onError.first.then((data) {
21+
print(data);
22+
23+
if (data is List) {
24+
Zone.current.errorCallback(data[0], data[1] as StackTrace);
25+
}
26+
});
27+
Isolate.spawn(serverMain, StartConfig(i, argResults),
28+
onError: onError.sendPort);
29+
}
30+
} on ArgParserException catch (e) {
31+
stderr
32+
..writeln('fatal error: ${e.message}')
33+
..writeln('usage: bin/main.dart [options...]')
34+
..writeln()
35+
..writeln(argParser.usage);
36+
exitCode = 1;
37+
}
38+
}
39+
40+
void serverMain(StartConfig config) {
41+
var app = Angel(
42+
//logger: Logger('tfb'),
43+
);
44+
45+
// hierarchicalLoggingEnabled = true;
46+
47+
//app.logger.onRecord.listen((rec) {
48+
// print(rec);
49+
// if (rec.error != null) print(rec.error);
50+
// if (rec.stackTrace != null) print(rec.stackTrace);
51+
//});
52+
53+
app
54+
.configure(dart_angel_benchmark.configureServer(config.argResults))
55+
.then((_) async {
56+
var http = AngelHttp.custom(app, startShared);
57+
var server = await http.startServer('0.0.0.0', 8080);
58+
var url =
59+
Uri(scheme: 'http', host: server.address.address, port: server.port);
60+
print('Instance #${config.id} listening at $url');
61+
});
62+
}
63+
64+
class StartConfig {
65+
final int id;
66+
final ArgResults argResults;
67+
68+
StartConfig(this.id, this.argResults);
69+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
postgres:
2+
username: benchmarkdbuser
3+
password: benchmarkdbpass
4+
host: tfb-database
5+
port: 5432
6+
databaseName: hello_world
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mongo_db: "mongodb://tfb-database/hello_world"
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
import 'dart:math';
4+
import 'package:angel_configuration/angel_configuration.dart';
5+
import 'package:angel_framework/angel_framework.dart';
6+
import 'package:args/args.dart';
7+
import 'package:file/local.dart';
8+
import 'package:http_parser/http_parser.dart';
9+
import 'package:mongo_dart/mongo_dart.dart';
10+
import 'package:mustache4dart/mustache4dart.dart' as mustache;
11+
import 'package:postgres/postgres.dart';
12+
import 'src/models/models.dart';
13+
import 'src/query/query.dart';
14+
15+
AngelConfigurer configureServer(ArgResults argResults) {
16+
var rnd = new Random();
17+
var minQueryCount = 1;
18+
var maxQueryCount = 500;
19+
var worldTableSize = 10000;
20+
var fs = const LocalFileSystem();
21+
22+
return (Angel app) async {
23+
// Load configuration.
24+
await app.configure(configuration(fs));
25+
26+
// Set up the view engine.
27+
var fortunesTemplate =
28+
await fs.file('views/fortunes.mustache').readAsString();
29+
30+
app.viewGenerator =
31+
(name, [data]) => mustache.render(fortunesTemplate, data);
32+
33+
// Select a querier, either MongoDB or PostgreSQL.
34+
//
35+
// Either way, the container *must* contain a `Querier`.
36+
if (argResults['type'] == 'mongo') {
37+
var db = Db(app.configuration['mongo_db']);
38+
app.container
39+
.registerSingleton<Querier>(MongoQuerier(db, rnd, worldTableSize));
40+
await db.open();
41+
app.shutdownHooks.add((_) => db.close());
42+
} else if (argResults['type'] == 'postgres') {
43+
var postgresConfig = app.configuration['postgres'] as Map;
44+
var connection = PostgreSQLConnection(
45+
postgresConfig['host'],
46+
postgresConfig['port'],
47+
postgresConfig['databaseName'],
48+
username: postgresConfig['username'],
49+
password: postgresConfig['password'],
50+
);
51+
app.container.registerSingleton<Querier>(PostgresQuerier(connection));
52+
await connection.open();
53+
app.shutdownHooks.add((_) => connection.close());
54+
} else {
55+
throw UnsupportedError('Unsupported DB ${argResults['type']}');
56+
}
57+
58+
// Always add a Date header.
59+
app.fallback((req, res) {
60+
res.headers['date'] = HttpDate.format(DateTime.now());
61+
});
62+
63+
// JSON response.
64+
app.get('/json', (req, res) {
65+
res.serialize({'message': 'Hello, World!'});
66+
});
67+
68+
// Plaintext response.
69+
app.get('/plaintext', (req, res) {
70+
res
71+
..write('Hello, World!')
72+
..close();
73+
});
74+
75+
// Fetch random world object.
76+
app.get('/db', (req, res) async {
77+
var querier = req.container.make<Querier>();
78+
res.serialize(await querier.getRandomWorld());
79+
});
80+
81+
// DB queries
82+
app.get('/queries', (req, res) async {
83+
// Get the querier and query count.
84+
var querier = req.container.make<Querier>();
85+
var queryCount =
86+
int.tryParse(req.uri.queryParameters['queryCount'].toString()) ??
87+
minQueryCount;
88+
queryCount = queryCount.clamp(minQueryCount, maxQueryCount);
89+
90+
// Fetch the objects.
91+
var worlds = await Future.wait<World>(
92+
List.generate(queryCount, (_) => querier.getRandomWorld()));
93+
res.serialize(worlds);
94+
});
95+
96+
// DB updates
97+
app.get('/updates', (req, res) async {
98+
// Get the querier and query count.
99+
var querier = req.container.make<Querier>();
100+
var queryCount =
101+
int.tryParse(req.uri.queryParameters['queryCount'].toString()) ??
102+
minQueryCount;
103+
queryCount = queryCount.clamp(minQueryCount, maxQueryCount);
104+
105+
// Fetch the objects.
106+
var worlds =
107+
await Future.wait<World>(List.generate(queryCount, (_) async {
108+
var world = await querier.getRandomWorld();
109+
world = world.copyWith(randomNumber: rnd.nextInt(worldTableSize) + 1);
110+
await querier.updateWorld(world.id, world);
111+
return world;
112+
}));
113+
res.serialize(worlds);
114+
});
115+
116+
// Templating
117+
app.get('/fortunes', (req, res) async {
118+
var querier = req.container.make<Querier>();
119+
var fortunes = await querier.getFortunes();
120+
121+
// Insert an additional fortune.
122+
fortunes.add(
123+
Fortune(
124+
id: 0,
125+
message: 'Additional fortune added at request time.',
126+
),
127+
);
128+
129+
// Sort the fortunes.
130+
fortunes.sort((a, b) => a.message.compareTo(b.message));
131+
132+
// Render the template.
133+
res.contentType = new MediaType('text', 'html', {'charset': 'utf-8'});
134+
await res.render('fortunes',
135+
{'fortunes': fortunes.map((f) => f.copyWith(id: f.id.toInt()))});
136+
});
137+
};
138+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'package:angel_serialize/angel_serialize.dart';
2+
part 'fortune.g.dart';
3+
part 'fortune.serializer.g.dart';
4+
5+
@Serializable(autoIdAndDateFields: false, autoSnakeCaseNames: false)
6+
abstract class _Fortune {
7+
num get id;
8+
9+
String get message;
10+
}

0 commit comments

Comments
 (0)