Alright boys, I'm back. In the previous video, we built the monitor command down, down, down, down here, which basically allows other clients to monitor the server and every command that runs on the server. And yeah, we built that in the previous video.
In this video, we're going to build the info command. And the info command basically just returns a bunch of information about the server, right? So let's do that.
Let's first of all create a new file over here. Call it `info.go`. Package main. Everything is in package main. Let's create an info struct as well. This is going to have a bunch of categories of different information.
So we want to return information about the server. This will be a map of strings. We also want to return information about clients connected to the server. This will be in the client category. We also want to return information about the server memory, like how much memory is being used, how much memory is allowed to be used, and so on. So we'll call this memory.
Then some information about data persistence, like RDB and AOF persistence methods. And then finally, just some general stats about the server.
Now let's create a new info function which will return an info struct pointer and return an empty info struct over here.
Now let's create a function over here for the info. This will be a pointer receiver, and we'll call this build. And this will take the app state as well because we're going to use the app state to get all sorts of different information about the server, which we will return in this info struct.
So first of all, let's just build the server category. This will be a map of strings, obviously. String, there we go.
First of all, we want to return the Redis version. This is the version of Redis that you're using in this server. Now, since this is our own custom Redis server, we don't really need to return anything over here. So I'm just going to say 1.0.0, right? That's the initial version of this program.
Then we also want to return the process ID, so the system process that is actually running this program, right? And you can actually get that very easily in Go by using the `os` package and calling the `Getpid` function. This is going to return a number, an integer, so to convert that to a string, we can call `fmt.Sprint` over here. There we go. And this will basically just convert whatever we give it into its string value, right?
Then we can also return the TCP port where this server is running. Now, since this is hard-coded for now, we haven't actually added any configuration to the TCP port, we can just add the hard-coded port over here as well, which is 6379.
Then we can also return the server time usec, which basically means the current server time. And to get this, we can return the `time.Now().UnixMicro()` function call. We're using UnixMicro instead of Unix, right? Instead of Unix, we're using UnixMicro because this will return the microseconds, not the seconds, right? And Redis also returns the microseconds in this server time usec field instead of returning the seconds. So obviously, convert it into a string as well. There we go.
Now we also want to return the uptime in seconds, so how long the server has been running in seconds. Now, this is the point where we actually need to use the app state. We want to be able to tell when the server actually started, right?
So let's open up the app state over here, and down here, let's add a field. Call it `serverStart`, which can just be a `time.Time`. There we go. And then down here, when we create the actual app state, we can just say `serverStart = time.Now()`. Awesome. So we create the new app state when we actually start the server, right? So we can just add a `time.Now()` over here, and that will be the time when the server was actually started.
Then we can use that over here as well. You can simply say `time.Since`, so how much time has passed since this `state.serverStart`, and then we can just grab the number of seconds from this. This is going to be of type duration, or actually not, of type float64, so we want to convert this into an integer first of all. And now we can convert it to a string.
The reason we first convert it into an integer is to just round it off, right? Round it off to the nearest whole number or whatever, and just give us a whole number of seconds, an integer of seconds, and then we just convert that into a string, right?
Next, we want to grab the executable, so the actual executable path that is running this server. I'll call this `exePath`, and I'll declare it up here. And again, this is super simple to grab in Go. We can just say `exePath` and error is equal to `os.Executable()`. This is going to return the path name, so the path of the executable that is running this program, right?
And obviously, then let's handle the error as well. If there is an error, let's just say that the exe path is equal to an empty string so it doesn't cause any errors for us, right?
Then finally, let's pass in the config file as well over here. And this is going to be the path of the config file that is being used in this server. Again, we can store this in the app state, again, which is over here. You can say something like `configFilePath`, or actually, instead of storing it in the app state, we can actually store it in the config struct over here. That probably makes a lot more sense, right? Since this is the config struct, we can store the `configFP` over here, which will be a string.
And where do we actually create this? Let's save this app state as well. Let's go over to `main.go`, and here we are in the `readConf` function. This is where we actually create the config file. So down here, once we've actually opened up the file and we know that this file path exists and we can actually successfully open this file, right? Then down here, let's just say `conf.configFilePath = fn`, which is the string.
And again, we could just do this up here as well, but then we, where is it? But then we don't know whether this file path actually exists or not, right? So we add it down here after this `os.Open` call and after we've handled all the errors and stuff, so we know that this file exists and is readable, right? Then we just assign the config file path over here.
And then in the info, we can just say something like `state.config.conf.configFP`, and that's the path of the config file.
Now, for the client, there's plenty of different fields that we can return over here, but I'm just going to return a single one. I'm going to return the connected clients over here. And again, we're going to need to use the state to actually track this.
So I can just say something like client count, which will be an integer. And we can track this in main. Where is it? Down here in the handleCon function.
So let's implement this. We can say down here, we know that when this handleCon function is run, there's a new client that just connected to the server. So we can just increment client count over here, right? And we also know that every time this function ends, once this function ends, the actual current client closes the connection. So we can just add a defer over here to decrement client count, and that's all we need to do to track the number of connections over here.
And then we can just say state.clientCount and obviously convert it into a string as well. There we go, and that's all we need to do to return the connected clients. And again, we can add a bunch of different information to this client category over here, but I'm just going to return the connected clients right now.
For the memory category, we're gonna have to use a new, a different third-party package over here to basically return a bunch of information about the memory, namely just the total amount of memory in this system.
So let me first of all shut down this server. We're going to use this third-party package called GoPSUtil, right? You can check it out on GitHub as well. It's a very popular package. It has 11.5 thousand stars. This is a very popular third-party package, and it's just going to basically return a bunch of PSUtil functions that you can use. PSUtil just means process utility, and it can just basically return a bunch of things like the memory, the CPU, and a bunch of other information about system processes, right?
The reason I'm grabbing it is to basically get the total amount of system memory, right? Because surprisingly, Go doesn't give you some sort of an API or whatever, or a function to get the total RAM in a system.
So what we can do is say `go get` and paste in this URL over here. This is the package name. And just to be completely, like, just to follow along perfectly fine with me, you can get the 4.25.8 version of this package. Run this. It's going to fetch the package from GitHub, download it, and oops, I guess that didn't work. Oh wait, I forgot to add a `v` over here. So this package name at v4.25.8. Do that, and it should work this time. Hopefully, or I'm gonna have to record another video for this one. Okay, there we go. It's working.
So we grab this package now. Let me just clear everything, and now we can actually get the total system memory. So up here, let's say memory and error equals mem, which is over here, this package dot virtual memory. Let me just call this memory so we don't, like, conflict with the mem keyword over here. This is going to return a virtual memory stat struct pointer and an error.
First of all, let's just handle the error. If error does not equal to nil, then we can say the memory total is equal to zero, and we can declare a memory total variable up here, which will be a positive, so an unsigned int64, right? Because no system is going to have negative memory, right? So we can use the unsigned integer over here, and I use 64 bits because bytes can be very large. If you have something like 32, 64 gigs of RAM, that's going to be a very, very huge number in bytes, right? So make sure to use 64 over here as such.
And if there's an error, then the total memory can just be zero. Otherwise, if everything went successfully, then you can say memTotal equals memory, and you can get a bunch of different information about system memory from this virtual memory struct. There's a ton of information over here, like total memory, used memory, swap memory, reclaimable memory, and all sorts of other things. We're not going to go through all of them, but there's a lot of stuff here that you can use in your own code, right?
We just want the total memory, and then we can add it to the memory category in info over here, right? So let's do this. First of all, let's just return the current memory that we're using, and this is going to be in the database mem field. Ah, there we go, and as always, convert it into a string by using `fmt.Sprint`.
Then we also want to return the use memory peak, right? This is going to be the highest amount of memory that we've ever used during the running of this server, right? So the absolute peak that we've hit in memory usage. So again, `fmt.Sprint`, and we actually need to track this.
And the way we can track this is in the `db.set` functions. We can first of all go over to db.go over here, and in the set function down here, we can actually track the memory peak. But first of all, let's go over to app state and actually add a field over here. Call it peak memory. This will be an int64.
Then let's go back to db.go, and down here, let's say if the db.memory is greater than state.peakMemory, so if we hit a new peak in memory usage, then we can say state.peakMemory is equal to db.memory. There we go, and that's all we need to do. Save that.
And then we can just say over here in info, state.peakMem is equal to this use memory peak field. Then let's return the total system memory as well. This is the memTotal that we just declared up there, right? And of course, convert it into a string.
And then let's also return the max memory, which will be in our config, in state.config.maxMemory. And then also the max memory policy, so the eviction policy that we're using for this server. Let's say `fmt.Sprint`, or actually we don't need to use `fmt.Sprint` over here because this is already a string, I think. There we go. Or I guess not because this is an eviction type, right? Which under the hood is a string, but I guess, yeah, same thing.
So that's all we're going to return for the memory category over here. And that's all I'm going to do for this part of the series.
We basically created this info struct, and we added the information for the server, the client, and the memory. In the next part of the video, or in the next part of this series, I'm going to finish up this info command. We're going to add information for the persistence category and the general category. And then we're going to create a handler for the info command, and then we'll try running it and see if it works and all of that stuff, right?
And this will probably be the final part of this series, right? The next part, where we finish this info command. That's probably gonna be the final part of this whole project. So we'll have like 20 videos in this project series, and then we'll just end it over there.
We built so many features in this project, and it's been such a fun time building all of these things, learning all of these new things and all that. But yeah, all good things end, and the next part of this series is going to be the end.
Thank you for following along all this time with all of these videos, and yeah, thanks for watching, and I will see you in the next video, which will be the final part of this series. Stay tuned, and see ya.