Deep dive into Android Studio Profilers (Android Dev Summit '18)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC PLAYING] DAVID HERMAN: Hello, everyone. Welcome to the deep dive into Android Studio Profilers session. I'm David Herman. SHUKANG ZHOU: I'm Shukang Zhou. We are engineers from the Profiler team in Android Studio. DAVID HERMAN: So before we really got into it, we wanted to give you all an outline of what to expect in this talk. Instead of sort of coming from a high overview, instead we're going to more narrowly focus on a few features that we think can help you get a better handle on any codebase. We're also going to drop some tips and tricks along the way. We're going to be profiling a real app, Santa Tracker. Santa Tracker is an app by Google which allows users to track Santa on his course around the world on Christmas Eve. The app also contains games, and a couple of other extras. They release a new app every year. We're going to be using the one that is publicly available on GitHub. Finally, I want to mention two talks about profilers that were previously given this year, one at Google I/O, which did talk profilers at a higher level, also introducing what was new in Android Studio 3.2 for profilers. And another one at the Android Game Dev Summit, about profiling your games. That talk focuses a little bit more on performance, native code, related tools, things like that. You can find those videos on YouTube. If you're watching this online, we've also included links to those videos in the video description below. If this is your first time learning about profilers-- you're just curious-- here's a very quick overview. The Studio Profilers feature is divided into four main profilers, one for CPU, one for memory, one for network, and one for battery. There's also an Event Profiler that's always on the top, which lets you see things like user events such as taps, keyboard events, screen rotations, and lifecycle events-- so when you're fragments and activities start and stop. Anyway, that's enough talking. Let's jump into the demo. SHUKANG ZHOU: OK, let's start the demo. So first, with one click on this button, I can launch the app and start profilers. So today, we are using Android Studio 3.4 Canary 3, because that's the latest of what we have. Since this is a Canary release, it's not a stable release yet. So there might be some bugs. Please bear with us if anything interesting happens today. The app we are using today, the Santa Tracker, contains only Java code. So the Java apps will be the focus of today's talk. So as you can see in the profiler, we have four profilers, CPU, Memory, Network, and Energy. You can click on any of them to get more detailed data from that profiler. Let's jump to the CPU profiler. So here, as you can see in the CPU profile, you can see the CPU utilization and the thread state. It will be useful to tell you when your app becomes CPU-bounded. And if you are examining two related threads, one trick is-- here-- you can reorder the threads any way you want. To know further data information about which part of your code is executing, or how they are executing, you need CPU recordings. And let me get a little bit more space. So as you can see here, there are four types of CPU recordings. So let's go to the first one. It's the sample Java method. Let's get a quick recording on this one. So during this type of CPU recording, the Java Virtual Machine periodically will collect the call stacks of all the other Java threads in your process. And then it will present the stacks in this part, which is the details of the recording. And after the recording is done, the entire recording is automatically selected. And if you want to take a closer look, there is a button called Zoom to Selection. So if I click it, it will fill the entire screen. And if you want to just see a subrange of the recording, you can select using your mouse. If you want to select the entire recording again, you can click this small clock icon here. And sometimes you only want to see a very specific point. In that case, you can to a single click in this area, and you automatically select something here. Now let's take a look at the call stacks here. And let's select this range. That might be more interesting. So as you can see, the profiler will color the cost from the Android platform in orange, the method from the Java language in blue, and it will also color any of your code and the library code to green. So if you want to know what method from your code is running, you will be looking for the green stuff. So here we see some green stuff here. And here, if you can see, this is onDraw method from the Village View class. And if you see the codebase, as you can easily see, the visit [INAUDIBLE] is responsible to draw the clouds that is on this screen. So another thing I want to talk about the sampling is you can do some customization about this recording type. So you click this Edit Configuration entry, and you can click this plus sign. You are able to create customized CPU configuration. For this sample Java method recording type, you can change the sampling intervals. So as most sampling techniques go, the more frequently you collect samples the more likely the data will be representative. However, that will incur more overhead. So sometimes, depending on your use case, you may want to have several tries before you find the sweet spot. So as we have seen, the sample Java method is very useful to get a high-level picture of which part of the code it is running. In some other scenarios, if you want to focus on a smaller area, the second type of the recording, trace Java method, will be more interesting. So let me collect another case. So in this type of CPU recording, the Java Virtual Machine is collecting the data every time when the execution enters a method or exits a method. So therefore, there is a lot more data being collected. So for example, if you really want to try and understand exactly what methods from my code are running, I will look for the green stuff here. And you can see there are a lot of things. And you may want to zoom in. So you can use the W-A-S-D keys to zoom in. So the W will be the zoom in. As you can see, the key I'm pressing. The S will be zoom out. The D will be moving to the right. A will be moving to the left. So if you're a gamer, you probably already know this. If you zoom in, let's keep zooming in to see what are these green stuff. So here we seen in this, a big one is the one we just saw before. It's the onDraw method from the Village View. They are responsible for drawing the clouds. Here, this is the onDraw method from the Snowflake View class. They are responsible to draw all these snowflakes that are floating around. And if you look closely, there are many very tiny green lines here. So you can further zoom in. You can further zoom in. So you can see this is the onDraw method from the Snowflake class. This is also from the snowflake class. This is also from Snowflake class. So you may want to see what exactly does this class go. So you Right-Click here, and you can have a menu to jump to the source code. So you can see here, in this Snowflake class, in this onDraw method, we are doing some calculation about the velocity, about an angle, about the size. Then we draw a circle. Then it's clear that every snowflake on this screen will execute this method. That's why we are seeing so many calls into this method. So as you can see, trace Java method is very useful to verify whether or not a method has been executed. It's also useful to verify how often that method is executed. Another thing I want to talk about here is about the four caps here. So the first one is the call chart. So the call chart, as we said, is representing all the call stacks during the CPU recording. So the things to the left-- well, let's select the entire range. You can see it. So the call stacks show up the left, where it happens first in this recording. So the things to the right will happen later in this recording. In the frame chart, it7s similar to the call stacks, but it's upside down. So the root is at the bottom. And also, identified call stacks are aggregated here. So it's very easy to see the total time of method has been executed. The top-down is [INAUDIBLE] has exactly the same data as the frame chart. It7s just represented in a different view. So what's nice about this view is you can sort of these methods by the time. So the save time is the time executing by this method itself. The children is the time executed by the subroutines called by this method. And the total column is the combination of the two. Bottom-up is looking at the call stack upwards to the caller, basically from the method to another function that called it. So here, it's very useful. If you expand it, it's very useful to see where this method is called, and how much time this method has executed when it is called by that specific caller. And there is a third type of CPU recording that is sample native functions-- C or C++ functions. And let's do also another short trace here. And if you remember, I just said this app, Santa Tracker, it contains Java-only code. So therefore, the call stacks, assuming the call stacks collected by this type of recording is not very interesting. They are mostly the system calls. Some of them use the Android framework native code. So however, if your app has any native components, this type of recording will be very handy. There is another type of CPU recording called trace system to our calls. But before I go into the details of that, I will hand the demo back to Dave. DAVID HERMAN: Excellent. So CPU recordings are very useful. However, sometimes there's an exact function, or maybe just a couple of functions that you know you want to analyze. And it's a little bit imprecise to record, do something in your app, stop recording, and then zooming in and search for it. Fortunately, we provide a very simple solution for this. And I already added the code in. So let's talk about it. So the debug class is actually part of the Android API. And the debug class and many of its methods have, in fact, been in the Android API since the very first version, including these two. What the start method tracing function does is it asks the system to take a trace, save it with the filename you provide, and then it puts it in a folder somewhere that you can pull off your device later and inspect. That's really nice, but on Studio profilers, we've got your back. We'll do all of that work for you. So if you were doing this manually, you'd probably be very careful about the name that you chose. And if you're doing multiple traces, you'd maybe choose unique names so that they didn't overwrite each other. In our case, the name is not going to show up in Studio. We don't really care about it. So call it whatever you want. Here, I'm going to start doing it putting a recording around this function. So what I'm curious about in this case, there's an activity in Santa Tracker called the City Quiz. And the City Quiz loads files from the disk. That's usually a really good thing, where you want to know how long it's going to take, and if it's doing anything suspicious. Maybe it's not. We're going to take a look. But one thing I do want to mention is when you call this start method tracing function, it's doing a trace of your code. This is the more expensive, precise, detailed one. So I'd be very careful. I wouldn't do this around a large amount of code, just to make sure that it doesn't take longer than you might expect. All right, let's actually go into the City Quiz, which is down here. See what happens. Now, before I do this, I want you to keep your eye over here in the Session panel. And you hit Play. It7s going to run in. And it's automatic going to record. I didn't have to do anything. That's awesome. Let me back out. So as you can see here, there's nothing new. This is exactly what Shukang was just showing you before. It's just another trace. But you didn't have to record it yourself. I think this is a good moment to call out the lifecycle events I talked a little bit about at the beginning of the talk. As you can see, we're doing a load here. We left our previous activity. And now we're into this new activity. You can also see that if you put your mouse over an activity, as of Android Studio 3.3, it will also include the fragments that are active during that time. So that may be useful for you. But here we are. We're in this space while we're still loading, before we've actually entered the activity. And there's another really great feature that I want to show you here, which is this Filter button. When I press the Filter button, it brings up a search box where I can type into it. Now I happen to know that JSON has a function called readLiteral. So let me just type that in. Now one thing you might notice here is this part of my call chart dimmed out. And all parts here are not dim. They're still the normal color. So let me zoom in and see if I can find where that is. So there we go. So we could see some instances of this readLiteral function. Basically if my function is an exact match, it will bold. If it's a function that calls either into that function or is called out of that function indirectly, the color will be left the same. And otherwise, it will dim. And it's really useful to sort of get a good overview of how much time you're spending in your code on the function that you care about versus what you don't have to pay attention to. All of the CPU detail views support this. So the flame chart has this similar dimming. Top-down and bottom-up will strip out those functions that were dimmed. So if you're ever trying to inspect some sort of method, and you're really narrowing down on it, please give the filter option a try, and see if you can sort of focus on what you're looking at. Now the last thing to call attention to here is, all of these traces that we've done, if you mouse over them, there is a Save button here. So you can actually export your traces. If you do this, you can give it to a co-worker, attach it to a bug-- useful things like that. If somebody gives you a trace file or you are loading one, you can just hit the plus button over here, and load it from file. All right. OK, cool. SHUKANG ZHOU: Now I'm going to talk about the last type of CPU recording, the trace system calls. Trace system calls, on this feature, was introduced in Android 3.2. It collects fine-grained system events that's related to an app7s performance. So you can investigate how your app interacts with the system resource so that's a system trace. So one thing-- again, I'm using the Click to Zoom button here so that it's very easy to see. One thing I want to show you here-- the first thing I want to show you is in this thread state view. So if you click the range, zoom to that selection again, and I will click here. [INAUDIBLE] So here, as you can see, you can use your mouse, hover over, you can see this thread state is runnable, become running, then become runnable, become running again. So as you can see, we are collecting every CPU scheduling operation. So at this level of details, it's very easy for you to figure out exactly when your thread becomes blocked. And that could be useful if you have some threading issues. Another thing I want to show using the trace system costs is to investigate slow UI jank. So slow UI rendering, also called jank by some people, as you may know, that is the [INAUDIBLE] UI does the work in two phases. The first phase happens in the main thread. It determines what is on the screen, including the layout and the [INAUDIBLE].. It determines the what by executing all the UI elements, such as all the view classes in your app. So after the main threads generate the what, they are passed to the native render thread. The render thread will be figure out how to draw them. Then the how will be passed to the surface finger system process, and the hardware who is actually performing the drawings. So that is giving that background so we can see under the frame area, we have this main, which represents the main thread, the render represents the render thread. So let me zoom out a little bit. So you can see here, this is the first phase. And corresponding, the second phase will be here. If you are targeting a smooth UI-- smooth animation at 60 frames per second, which is roughly 16 milliseconds per frame. So the two phases combined together should be under 16 frames. If it is longer than that, the profiler will color that frame to red. So as you know, this is something slow. So if we zoom out more, you can see a lot of frames. I think every one today is red. That means every one is exceeding the 16 millisecond threshold. One factor is we are using the emulator. Because the way the emulator interacts with the system, you are going to see more red frames than from our actual device. Before today's talk, I have collected another trace using an actual physical device. And I have exported that trace as a file. And now I want to import that trace to show you. So as Dave said before, you can use this plus sign to import a trace. That7s this trace. Because when you trace system calls, everything-- the system-wide events are collected. So you need to tell the provider which process you want to look at. So we want to look at Santa Tracker. And as you know, the Linux from the system point of view, every process or every thread, your name can be no longer than 15 characters. So that's why this one is actually the santatracker.debug. For some reason, the system thinks this is the name. So if you select this one, you are going to import this trace. And here, you can see from the actual device, most of the frames will be in gray. That indicates that they are under 16 millisecond threshold. And so6e of the frames are in the red because they are over that threshold. You may wonder, how does the profiler know how long these phases are taking? That is from the tracing point. So Android platform engineers have added building tracing points into some of the critical tasks in the Android system. So the example here is, if you click the main thread here, these events are showing from the trace events here. So if I zoo6 in here, you will see this. These are trace event called Choreographer doFra6e. So that's the first phase we just talked about in the UI rendering. It happens in the main thread. If you click the render thread, you can see there is an event called drawFra6e. So that's the second phase. And you can also see there are other tracing points in the system. They are all building syste6s. So they are available on any Android phones, because they are building. And they are very useful to get the timing information for some specific tasks. And actually you can have your own trace points, too. And I'm going to demo that here. So today-- so here, if we go into the top of the app, come back to this view, we have the cloud. We have the snowflakes. I want to know exactly how long 6y code is spent joining them. So for the cloud, I go to the Village View. So there should be a onDraw method. So at the beginning of the method, I add the instrumentation. The trace beginSection, you need to provide a string, which is the section's name. At the end of the method, I will end this trace event. For the snowflakes, it goes to the Snowflake View. Again, it should be in the onDraw method. I want to point out the section name. You should pick a name that 6akes sense to you so you can recognize when you7re doing the CPU recording. So here we are doing the Snowflake Android Dev Su66it 2018. So that makes sense for today's demo. So now I have added the manual instrumentation. So I will rebuild this app, and re-profile it. So while we are waiting for the build, you may be wondering, if I want to know how long the two methods are taking, why don't you use the trace Java method, that type of CPU recording you are talking about a moment ago? So I would say trace Java method is very powerful. It's very easy to use. But it has significant overhead, because the Java Virtual Machine is collecting data every time when the execution enters a method, and every time when the execution exits a method. So if you have a lot of frequent small method calls, that overhead can quickly add up and become very expensive. If you use the 6anual instrumentation, for example like using the trace API, you have the full control of when and where to collect data. So if you use that wisely, the overhead will be much smaller. And as a result, the data you collected will be more accurate. So let's verify the instrumentation we have. So you go to the CPU Profiler. We collect a trace of trace system calls. And here, we zoo6 in. We see this event again, choreographer doFra6e. This is from the main thread that is responsible for all UI elements. And if you keep zoo6ing, you can see this is the frame we just added, Village View, who is joining the cloud. And this is the Snowflake View, ATS 2018. That's the thing responsible for joining the snowflakes. So this is how to use the trace system calls CPU recording. So you may have heard of a very similar tool called systrace. So actually my Google co-worker [INAUDIBLE] gave a lightning talk yesterday on the systrace. So it's an extremely powerful tool, but the learning curve of the systrace is a little bit steeper than the Android Studio Profiler. So you may choose the tools that best suits your needs. OK, that's for the trace CPU recording. I will hand over the demo back to Dave. DAVID HERMAN: All right. Let's leave the CPU Profiler behind, and jump over to the Memory Profiler. First of all, I want to draw everyone's attention to this allocation tracking pull-down. Some quick history here, in Android Studio 3.0, when targeting Android devices with O or newer, what we did is we would collect a call stack for every single allocation that your app made. We did it because it would be very convenient to use to have that history. However, some of our users reported that profilers were slowing their app down. After we investigated, it turned out to be this feature. So starting in Android Studio 3.3, we now give you the option to configure this. Let me go ahead and look here. So we have None, Sampled and Full. None disables the feature, Full enables it, and Sampled attempts to collect a subset of the allocations. That sort of gives you a general look for how your app7s memory behavior is, while not actually affecting your performance as much as a full one does. Now that being said, whether or not Full affects your app or not can depend on the host machine you're running on, whether you're targeting a device or an emulator, or even your code. If your app code has a lot of small allocations in it like Santa Tracker does, it can be slow. But I recommend playing around with the features. I'm going to go ahead here and turn Full on. I76 going to go live here. So you notice once I turned on Full, this allocation indicator started showing up. I want some interesting things to happen. So I'll just go ahead and rotate the device. I may grab you there. Rotate it back. move it back over there. So now let's go ahead and take a look here. So all I need to do at any point is just drag across, and I'm going to be able to see all of the allocations and the deallocations that happened during that range. This could be a really lightweight way to sort of get a quick look at what your memory is doing. There's an allocation and deallocation count. So sometimes you might be able to find memory leaks even just doing this. And then the other nice thing is maybe if you have this on, and you're doing some other stuff, you can actually go back in time and select a range, and still see what's going in your memory. Now I'm going to go ahead here and turn it back off again. And you'll notice that the allocation indicated that it stopped tracking by doing an empty circle. If I did Sampled Mode, it would put a half-filled circle. Now looking at these objects, once I click on it, you can see every single one that's currently allocated, as well as where it was allocated. This is very useful to get your handle on a codebase. You can click around this and see where different objects are coming from. That being said, it might not help you understand why a memory leak happened, what is holding on to my memory. If you want to get that information, you have to go over here to this icon-- this is the heap dump icon-- and click the heap dump. So what I'm actually going to do here, just to let you know, I could not find a leaking activity or fragment in the Santa Tracker app. So, full disclosure, I added one. And what I did is I added a memory leak to this Penguin Swim game here. So actually let's go ahead and go into the activity, and leave it. And let me actually do another heap dump. All right, so we're about to get a bunch of heap dump information here. So I know that the fragment inside this activity is called swim fragment. So just like CPU, we have the Filter button here. And I can filter out all of the different objects, and find that, yep, sure enough, this swimming fragment is still alive. And I'll click on it, I'll select the active instance. There is a lot going on here, so I'm going to take a moment to sort of explain a little bit. And let's just take a step back and take a moment to absorb it. So I really want to explain what this depth idea is. So imagine all your Java memory that can be cleaned up by the garbage collector lives in this heap. There are some special Java objects that live outside of this circle. Those are called GC roots. When you create a new thread, that basically creates this special GC root. Or static variables are an instance of a GC root. So what ends up happening is you might, in a thread, create an instance of some class that it, in turn, creates an instance of another class, and it, in turn, so on and so forth. And so basically what you're doing is you're creating this long chain of objects, and each item in that chain has further and further depth from the GC root. Now you may have heard that cycles are not a problem to the garbage collector. I can point at you, and then you point at you, and you point back at me. But if we can't all be reached, that will all get removed by the garbage collector. So one of the things to note is when you take a heap dump, you might see a scary amount of things holding on to my instance, but a lot of those are harmless. A lot of them are potentially just cycles. And one way that you can kind of know-- it's not always true, but it's a good heuristic-- is if the depth is greater than your own depth. So let me explain why that is. So imagine that the garbage collection root points to you. You create a child instance. And one of the things you do is you give it your this pointer. So it's pointing back at you. That's a very common thing. Here, you point back at me. You're my child, but you know who your parent is. So that means my depth is then going to be added onto that this pointer's depth. So that's a reason that some of these depths here may indicate that even though you're seeing that it's not an issue, you'll also notice here that there's a few of these items in the depth column where the depth is blank. That means that there's actually no way to reach these at the moment from any garbage collection root. So it will eventually be cleaned up. Even though it's showing up in the heap dump right now, you don't have to worry about it. Just ignore it. It's as good as reclaimed. Just to make sure we understand this a little bit, with a concrete example, I'm actually going to look here at this code, which is not the cause of our memory leak. Our depth is 1. This item's depth is 2. So what this is saying is somewhere there is an instance of a score view class. And that score view class has a variable called share ClickListener, whose value is me. So let's go ahead and take a look at swimming fragment. And I'm going to search for getScore view. So before, when I saw this, I was trying to look through the code. I said, there's gotta be some way that I'm going to get a score view that points at me. And there's this getScore view method inside my swimming fragment. If I jump to it, what you're going to see is, yep, we create one, and we pass in the this pointer. So that's sort of explaining why there is a cycle there. I don't have to worry about it, but at least it sort of explains why it's showing up in the heap dump. Now you'll also notice there's a lot of this$0 symbols. What's going on here-- because you're going to see it a lot. You all know what the this pointer is, but if you are an inner class-- if you're a nested class and you need to have access to your outer classes' fields, the way that it works is the compiler generates a synthetic variable. And instead of calling it this, because that's taken, it calls it this$0. So this$0 means the this that's one level out of my scope. And any time you create an inner class that has a reference to its outer class, or you create a closure, an anonymous class instance, it's going to have that. So if you're all using lambdas in your code, or anonymous classes, you're going to see a lot of these zeros. This one has a depth of 0, which means it's a GC root. That makes it really suspicious. So let's jump to the source. Now hopefully I might look at this and say, oh, I wrote this long-running task. As you can see, it's not a static class. It's a final class. So if it's not static, it's going to hold onto a reference to the parent class. That's what's going on here. And I might say, oh, this long-running task, I created it, but I forgot to cancel it. So let's go ahead and take a look here. Yep, sure enough, I left it commented out for no good reason. Let me uncomment it there. And let's relaunch. So what we're going to do now is-- let me just make sure it's saved. There we go. What we're going to do now is reboot it just to make sure that in fact this swimming fragment was released. One of the things to keep an eye out for when you're hunting for memory leaks is static variables or singleton classes that are holding onto your class or registering yourself with a listener, but forgetting to remove it, or any of these inner classes that for some reason may not end up stopping. And they're still running, even though the activity is trying to exit. Are we profiling here? Yes, we are still going. And then another thing I want to mention is it's not always going to be this easy. You're not always going to have this obvious culprit. So in that case, what you're going to want to do is, I would say, just try to get to know the code. Look for those things that I talked about. Let's go ahead and enter. And we'll exit here while I talk. And if you've cleaned it up, even if you didn't find it through the heap dump, the heap dump is still going to be the source of truth. It's still going to be the thing that guarantees to you that your memory is actually reclaimed. So I'm going to go ahead here, go back into the memory profiler. I'm going to click on the garbage collector. So you may have noticed these garbage collected events, automatically at the bottom. So for example, there's a lot there. That's when the garbage collector decided to collect on its own. But you can also click the garbage collection button to manually cause it to get run. Now we still see swimming fragment here. Let's see if the depth is actually-- so as you can see, swimming fragment is still showing up, which is-- I could be nervous. What happened? But the depth column here is blank. Everything is just basically waiting to be picked up by the garbage collector. You can press the garbage collector a few more times. One trick I like to do is rotate the phone, and then rotate it again. Anytime you rotate an Android phone, lots of fun things happen. It tells the garbage collector things are going on. So in that case, let's just do another heap dump here, and we'll see if the swimming fragment is truly well and collected. I wish this came with a drum roll. And there you go. It's gone. So the final thing I want to quickly talk about is, if you're looking at the heap dump, you're not always necessarily hunting a memory leak. You might not know some major class to look out for. So there's this concept of shallow size and retained size. So shallow size is the size of a single instance of some class that's been allocated. And retained size is all of the things that's holding onto. So what you may want to do is hunt-- sort your shallow size, sort your retained size, maybe investigate to see if there's any sort of suspicious memory things there, or maybe you could clean up your design and remove some memory there earlier. Anyway, that ends our demo. Hand it back to Shukang. Let's go to the slides. SHUKANG ZHOU: OK, cool. So to recap, in today's demo we have shown to use CPU Profiler and Memory Profiler to get a better understanding of the codebase of the Santa Tracker app. And to be honest, Dave and I don't know much about it before we are preparing for this talk. And we also have shown that we don't only use profilers to diagnose performance issues. We also use the profilers to help us understand the performance of this app. As we said before, there are also network and energy profilers in Android Studio. But unfortunately, we don't have time to cover them today. So please refer to our online documents and talks to learn more. So we hope you have learned some tricks and tips from our demo today. We hope they are useful when you approach your own codebase with your Studio Profilers at your site. Thank you very much for attending our talk. [APPLAUSE] [MUSIC PLAYING]
Info
Channel: Android Developers
Views: 27,209
Rating: 4.8579884 out of 5
Keywords: type: Conference Talk (Full production);, pr_pr: Android, purpose: Educate
Id: LGVbpobV-Yg
Channel Id: undefined
Length: 41min 27sec (2487 seconds)
Published: Thu Nov 08 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.