1 module bench;
2 import std.algorithm : count, filter, map, sum;
3 import std.array : array, split;
4 import std.datetime.stopwatch : StopWatch, AutoStart, Duration;
5 import std.file : exists, readText;
6 import std.format : format;
7 import std.stdio : stderr, writeln, writefln;
8 import ada.url;
9 
10 @safe:
11 
12 immutable string[11] urlExamples = [
13     "https://www.google.com/webhp?hl=en&ictx=2&sa=X&ved=0ahUKEwil_oSxzJj8AhVtEFkFHTHnCGQQPQgI",
14     "https://support.google.com/websearch/?p=ws_results_help&hl=en-CA&fg=1",
15     "https://en.wikipedia.org/wiki/Dog#Roles_with_humans",
16     "https://www.tiktok.com/@aguyandagolden/video/7133277734310038830",
17     "https://business.twitter.com/en/help/troubleshooting/how-twitter-ads-work.html?ref=web-twc-ao-gbl-adsinfo&utm_source=twc&utm_medium=web&utm_campaign=ao&utm_content=adsinfo",
18     "https://images-na.ssl-images-amazon.com/images/I/41Gc3C8UysL.css?AUIClients/AmazonGatewayAuiAssets",
19     "https://www.reddit.com/?after=t3_zvz1ze",
20     "https://www.reddit.com/login/?dest=https%3A%2F%2Fwww.reddit.com%2F",
21     "postgresql://other:9818274x1!!@localhost:5432/otherdb?connect_timeout=10&application_name=myapp",
22     "http://192.168.1.1",
23     "http://[2606:4700:4700::1111]",
24 ];
25 
26 struct BenchmarkData
27 {
28     const(string)[] urls;
29     size_t totalBytes;
30 
31     static BenchmarkData fromFile(string filename) @trusted
32     {
33         if (!filename.exists)
34         {
35             stderr.writeln("File not found: ", filename);
36             return BenchmarkData(urlExamples, urlExamples.array.map!(s => s.length).sum);
37         }
38 
39         auto urls = filename.readText.split;
40         return BenchmarkData(urls, urls.map!(s => s.length).sum);
41     }
42 
43     static BenchmarkData defaultData()
44     {
45         return BenchmarkData(urlExamples, urlExamples.array.map!(s => s.length).sum);
46     }
47 
48     size_t countInvalidUrls() const @trusted
49     {
50         return urls.filter!(url => !AdaUrl(ParseOptions(url)).isValid).count;
51     }
52 }
53 
54 struct BenchmarkResults
55 {
56     Duration totalTime;
57     double bytesPerSecond;
58     double urlsPerSecond;
59     double timePerByte;
60     double timePerUrl;
61     size_t paramCount;
62     string toString() const
63     {
64         return format("Benchmark results:\n" ~
65                 "Total time: %s\n" ~
66                 "Speed: %.2f bytes/s\n" ~
67                 "URLs processed: %.2f urls/s\n" ~
68                 "Time per byte: %.9f s\n" ~
69                 "Time per URL: %.9f s",
70             totalTime,
71             bytesPerSecond,
72             urlsPerSecond,
73             timePerByte,
74             timePerUrl);
75     }
76 }
77 
78 BenchmarkResults runBenchmark(const BenchmarkData data) @trusted
79 {
80     size_t paramCount;
81     auto sw = StopWatch(AutoStart.yes);
82     foreach (url_string; data.urls)
83     {
84         auto url = AdaUrl(ParseOptions(url_string));
85         if (url.isValid)
86         {
87             auto params = url.getSearchParams(url.getSearch);
88             paramCount += params.length;
89         }
90     }
91 
92     sw.stop();
93     immutable duration = sw.peek();
94     immutable double seconds = duration.total!"nsecs" / 1_000_000_000.0;
95 
96     return BenchmarkResults(
97         duration,
98         data.totalBytes / seconds,
99         data.urls.length / seconds,
100         seconds / data.totalBytes,
101         seconds / data.urls.length,
102         paramCount
103     );
104 }
105 
106 void main(string[] args)
107 {
108     auto data = args.length > 1 ? BenchmarkData.fromFile(
109         args[1]) : BenchmarkData.defaultData();
110     writefln("Loaded %d URLs, totaling %d bytes.", data
111             .urls.length, data.totalBytes);
112     writefln("Found %d invalid URLs.", data.countInvalidUrls());
113 
114     auto results = runBenchmark(data);
115     writeln(results);
116 }