Process Manager for Linux Phase 1
作者:互联网
Process Manager for Linux Phase 1
我的课设!
Requirement
这部分的设计要求是:
实现一个基于控制台的进程管理器,包含以下功能:
- 记录:将获取到的进程信息每一分钟保存到某个文档中,文档名称为当前的时间戳。
- 查找:用户输入要查找的进程ID,系统调用查找函数进行查找并显示结果。
- 显示:当用户输入指令时,或者每一分钟,显示当前的最新进程信息,包括进程的ID、进程的名
称、进程的位数以及进程的CPU占有率。 - 排序:可按照进程ID大小、进程名字、CPU占有率进行排序。
Code Design
整体上,我们给整个 Project 设置了这样的结构:
我们使用 Runtime
中的 class Loop
实现程序主题运行时刻的 Read-Evaluate-Print Loop 。
在 Item
中的一些类主要维护程序中涉及的各类数据。class Process
维护单个进程;class ProcessList
维护进程列表,同时实现更新、排序、查找等功能的接口;class Tokens
维护用户输入的指令信息。
此外,man_text
中用于集中存放一些手册、输出信息等内容,便于管理维护。iostaff
实现一些终端彩色输出,字符串标准化函数等输入输出杂项。
接下来我们分别看各部分的实现。
Utils
iostaff
// iostaff.h
#pragma once
#include <string>
#include <ostream>
class Form {
public:
int type;
Form(int x): type(x) {}
friend std::ostream& operator << (std::ostream &os, const Form &itm);
};
const Form endo(0), tab(-1),
bold(1), red(31),
blue(34), green(32),
highlit(43), italic(3);
int StringToInt(const std::string &s);
std::string NameStandlize(const std::string &s);
void PrintTableline();
// iostaff.cpp
#include <cctype>
#include <iostream>
#include "iostaff.h"
std::ostream& operator << (std::ostream& os, const Form& itm) {
switch (itm.type) {
case 0:
os << "\033[39m\033[0m";
break;
case -1:
os << "\t\t";
break;
default:
os << "\033[" << itm.type << 'm';
}
return os;
}
int StringToInt(const std::string &s) {
int ret = 0;
for (auto digit: s) {
if (!isdigit(digit)) {
return 0;
}
ret = ret * 10 + digit - 48;
}
return ret;
}
std::string NameStandlize(const std::string &s) {
std::string ret;
for (auto cht: s) {
if (isalpha(cht)) {
ret.push_back(toupper(cht));
}
}
return ret;
}
void PrintTableline() {
std::cout << endo << blue;
for (int i = 0; i != 80; ++i) {
std::cout << "-";
}
std::cout << endo << std::endl;
}
基于这里 Form
类的实现,我们可以通过类似于:
int a, b;
std::cin >> a >> b;
std::cout << bold << italic << red << a << " " << b << endo << std::endl;
的方式,方便控制输出粗体、斜体、字体颜色内容,其中 endo
可清空当前流中的格式。
Text
man_text
// man_text.h
void TableHeading();
void def_manual();
void WaitInput();
// man_text.cpp
#include <iostream>
#include "../Utils/iostaff.h"
void def_manual() {
using namespace std;
PrintTableline();
cout << bold << blue << "Command" << tab << "Args" << tab << "Function" << endo << endl;
PrintTableline();
cout << bold << green << "help" << endo << tab
<< "None" << tab
<< "Print the Command Manual"
<< endl;
cout << bold << green << "quit" << endo << tab
<< "None" << tab
<< "Quit the Task Manager for Linux"
<< endl;
cout << bold << green << "update" << endo << tab
<< "None" << tab
<< "Update the Process Infomation right now"
<< endl;
cout << bold << green << "find" << endo << tab
<< green << "[Pid]" << endo << tab
<< "Find a process by its pid, or other dimensions"
<< endl;
cout << bold << green << "sort" << endo << tab
<< green << "[COL] [ASC/DEC]" << endo
<< " Sort info by given coloum name in given order"
<< endl;
cout << bold << green << "display" << endo << tab
<< green << "[NUM]" << endo << tab
<< "Motify the number of processes to display"
<< endl;
PrintTableline();
}
void TableHeading() {
using namespace std;
cout << bold << green << "PID" << tab
<< "%CPU" << tab
<< "%MEM" << tab
<< "COMMAND" << tab
<< endo << endl;
}
void WaitInput() {
std::cout << "TaskM>>" << bold << green << "> " << endo;
}
这里只是基于 iostaff
输出了一些简单的文本信息。
Item
Tokens
// tokens.h
#pragma once
#include <istream>
#include <vector>
#include <string>
class Tokens {
private:
std::vector<std::string> CmdLine;
void AddElements(std::string s);
public:
void Print();
bool empty() const;
std::string GetHead() const;
std::string GetArgs(int ith) const;
friend std::istream& operator >> (std::istream& is, Tokens& itm);
};
// tokens.cpp
#include <string>
#include <iomanip>
#include <cstdlib>
#include <iostream>
#include "tokens.h"
#include "../Utils/iostaff.h"
void Tokens::AddElements(std::string s) {
CmdLine.push_back(s);
}
bool Tokens::empty() const {
return CmdLine.empty();
}
std::string Tokens::GetHead() const {
return NameStandlize(CmdLine[0]);
}
std::string Tokens::GetArgs(int ith) const {
if (ith >= CmdLine.size()) {
return std::string();
}
return CmdLine[ith];
}
std::istream& operator >> (std::istream& is, Tokens& ret) {
std::string buf;
std::getline(std::cin, buf);
buf = buf + ' ';
std::string::size_type ind = 0;
while (true) {
while (buf[ind] == ' ') ++ind;
auto pos = buf.find(' ', ind);
if (pos == std::string::npos) {
break;
}
ret.AddElements(buf.substr(ind, pos - ind));
ind = pos + 1;
}
return is;
}
void Tokens::Print() {
for (auto itm: CmdLine) {
std::cout << "[" << itm << "] ";
}
std::cout << std::endl;
}
只是一些简单的字符串处理操作。
Process
// process.h
#pragma once
#include <string>
#include <vector>
#include <iostream>
class Process {
private:
int pid;
int VirtualMem, ResidentSiz;
double Ratio_CPU, Ratio_MEM;
std::string RuningBits;
std::string UserName, CommandStr, tty, RunStat, start, SysTime;
public:
friend std::istream& operator >> (std::istream &ins, Process &itm);
friend std::ostream& operator << (std::ostream &ous, const Process &itm);
int GetPid() const;
double GetRatioCPU() const;
double GetRatioMEM() const;
std::string GetCmd() const;
// std::string& GetBit();
};
// process.cpp
#include <iomanip>
#include <istream>
#include <string>
#include "process.h"
#include "../Utils/iostaff.h"
int Process::GetPid() const {
return pid;
}
double Process::GetRatioCPU() const {
return Ratio_CPU;
}
double Process::GetRatioMEM() const {
return Ratio_MEM;
}
std::string Process::GetCmd() const {
return CommandStr;
}
// std::string& Process::GetBit() {
// return RuningBits;
// }
std::istream& operator >> (std::istream &ins, Process &itm) {
ins >> itm.UserName >> itm.pid >> itm.Ratio_CPU >> itm.Ratio_MEM;
ins >> itm.VirtualMem >> itm.ResidentSiz >> itm.tty >> itm.RunStat;
ins >> itm.start >> itm.SysTime;
std::ostringstream oss;
getline(ins, itm.CommandStr);
return ins;
}
std::ostream& operator << (std::ostream &ous, const Process &itm){
ous << itm.pid << tab
<< itm.Ratio_CPU << tab
<< itm.Ratio_MEM << tab
<< itm.CommandStr;
return ous;
}
这里原定是要通过 file -L /proc/PID/exe | grep -o '..-bit'
获取进程的运行位数的。但该批量执行时,可能出现需要 root
权限、进程 ID 无效、调用时间超限等问题。且考虑到 Linux 下多运行位数进程的特性并不显著,因此放弃了该功能的实现。
process_list
// process_list.h
#pragma once
#include <vector>
#include <functional>
#include "process.h"
typedef std::vector<Process>::iterator pit;
class ProcessList {
private:
std::vector<Process> lst;
std::string DefaultCol;
bool Defaultinc;
public:
ProcessList();
void UpdateList();
void PrintList(bool FoundFlag, int Siz = 10);
bool FindProcess(int TargetPid);
void SortCol(std::string col, bool increasing);
};
// process_list.cpp
#include <ctime>
#include <string>
#include <iomanip>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <functional>
#include "process.h"
#include "process_list.h"
#include "../Utils/iostaff.h"
#include "../Text/man_text.h"
ProcessList::ProcessList() {
DefaultCol = "CPU";
Defaultinc = false;
}
void ProcessList::UpdateList() {
using namespace std;
ostringstream oss;
auto CurTime = time(nullptr);
oss << put_time(gmtime(&CurTime), "%F-%T");
string LogFile = "Logs/" + oss.str() + ".log", Heading;
system(("ps auxc > " + LogFile).c_str());
ifstream FileIn(LogFile);
lst.clear();
Process itm;
getline(FileIn, Heading);
while (FileIn >> itm) {
lst.push_back(itm);
oss.clear();
}
SortCol(DefaultCol, Defaultinc);
}
bool ProcessList::FindProcess(int TargetPid) {
for (pit ite = lst.begin(); ite != lst.end(); ++ite) {
if (ite->GetPid() == TargetPid) {
std::rotate(lst.begin(), ite, ite + 1);
return false;
}
}
std::cerr << red <<
"Cannot Find the given process" << endo << std::endl;
return true;
}
void ProcessList::PrintList(bool FoundFlag, int Siz) {
using namespace std;
system("clear");
PrintTableline();
TableHeading();
if (FoundFlag) cout << bold << red;
cout << lst[0] << endo << endl;
for (int i = 1; i != Siz; ++i) {
cout << lst[i] << endl;
}
PrintTableline();
}
// Done with the help from Barry on stackoverflow.com
void ProcessList::SortCol(std::string col, bool increasing) {
DefaultCol = col;
Defaultinc = increasing;
auto sort_by = [&](auto projection) {
if (increasing) {
std::ranges::sort(lst, std::less(), projection);
} else {
std::ranges::sort(lst, std::greater(), projection);
}
};
if (col == "PID") sort_by(&Process::GetPid);
else if (col == "CPU") sort_by(&Process::GetRatioCPU);
else if (col == "MEM") sort_by(&Process::GetRatioMEM);
else if (col == "COMMAND") sort_by(&Process::GetCmd);
}
这里的 UpdateList
函数是基于 /bin/ps
获取进程信息。用 C++11 中的 std::put_time
结合字符串流操作,比较便利地处理了时间信息。
FindProcess
中使用了 std::rotate
将所查找的进程滚动至列表表头,便于后续显示输出操作。
SortCol
的实现使用了 C++20 新标准中的 ranges
库。最初实现时遇到了一些类型系统上的问题。在 Stack Overflow 上提问后,在 @Barry 的帮助下实现了该函数。具体参见 https://stackoverflow.com/questions/66901310
Runtime
loop
// loop.h
#pragma once
#include <mutex>
#include <ctime>
#include <thread>
#include "../Item/tokens.h"
#include "../Item/process_list.h"
class Loop {
private:
time_t Systime;
ProcessList prl;
bool ElementFound;
int DisplaySiz;
static const int WaitingThread = 30;
static const int WaitingUpdate = 60;
public:
Loop();
static int GetWaitUpdate();
static int GetWaitThread();
int& GetDisplaySiz();
bool& GetElementFound();
void EvalCmd(Tokens& cmd);
void LogsCheck();
bool UpdateTime();
void REPLoop(); // Read-Evaluate-Print Loop
};
// loop.cpp
#include <ctime>
#include <memory>
#include <string>
#include <future>
#include <thread>
#include <iostream>
#include "loop.h"
#include "../Item/tokens.h"
#include "../Item/process.h"
#include "../Text/man_text.h"
#include "../Utils/iostaff.h"
Loop::Loop() {
Systime = -1;
DisplaySiz = 10;
}
bool Loop::UpdateTime() {
auto new_time = std::time(nullptr);
if (Systime == -1 || new_time - Systime >= GetWaitUpdate()) {
Systime = new_time;
prl.UpdateList();
return true;
}
return false;
}
void Loop::LogsCheck() {
system("mkdir -p Logs");
}
int Loop::GetWaitUpdate() {
return WaitingUpdate;
}
int Loop::GetWaitThread() {
return WaitingThread;
}
bool& Loop::GetElementFound() {
return ElementFound;
}
int& Loop::GetDisplaySiz() {
return DisplaySiz;
}
void Loop::EvalCmd(Tokens& cmd) {
GetElementFound() = false;
if (cmd.empty()) {
std::cerr << red <<
"Error Evaluating a empty Command" << std::endl;
return;
}
if (cmd.GetHead() == "QUIT") {
exit(0);
}
else if (cmd.GetHead() == "UPDATE") {
auto new_time = std::time(nullptr);
Systime = new_time;
prl.UpdateList();
prl.PrintList(GetElementFound(), GetDisplaySiz());
WaitInput();
}
else if (cmd.GetHead() == "FIND") {
int arg = StringToInt(cmd.GetArgs(1));
if (arg == 0) {
std::cerr << red <<
"Error Evaluating the FindArgs" << endo << std::endl;
WaitInput();
} else {
if (!prl.FindProcess(arg)) {
GetElementFound() = true;
prl.PrintList(GetElementFound(), GetDisplaySiz());
}
WaitInput();
}
}
else if (cmd.GetHead() == "SORT") {
auto col = NameStandlize(cmd.GetArgs(1)),
ord = NameStandlize(cmd.GetArgs(2));
bool OrdFlag;
if (ord == "ASC") OrdFlag = true;
else if (ord == "DEC") OrdFlag = false;
else {
std::cerr << red <<
"Error sorting order input" << endo << std::endl;
WaitInput();
}
prl.SortCol(col, OrdFlag);
prl.PrintList(GetElementFound(), GetDisplaySiz());
WaitInput();
}
else if (cmd.GetHead() == "DISPLAY") {
int arg = StringToInt(cmd.GetArgs(1));
if (arg == 0) {
std::cerr << red <<
"Command argument invaild" << endo << std::endl;
WaitInput();
} else {
GetDisplaySiz() = arg;
prl.PrintList(GetElementFound(), GetDisplaySiz());
WaitInput();
}
} else {
def_manual();
WaitInput();
}
std::cout.flush();
}
void Loop::REPLoop() {
Tokens UserCmd;
GetElementFound() = false;
auto update_thread = std::thread([&] {
while (true) {
if (UpdateTime()) {
GetElementFound() = false;
prl.PrintList(GetElementFound(), GetDisplaySiz());
WaitInput();
std::cout.flush();
}
std::this_thread::sleep_for(
std::chrono::seconds(GetWaitThread()));
}
});
Tokens TempCmd;
while(true) {
std::cin >> TempCmd;
UserCmd = std::move(TempCmd);
EvalCmd(UserCmd);
}
update_thread.join();
}
这里为了实现自动刷新输出的同时等待用户输入的功能,我们在 144 行引入了一个自动更新线程。该线程每经过 int WaitingThread
秒苏醒一次,检查距离上次更新信息是否超过了 int WaitingUpdate
时间。其中 WaitingThread
是 WaitingUpdate
的一半。这一设置是考虑到用户可能会手动进行更新操作,可能会造成相应的影响。
不过这里的实现存在一个问题:如果用户正在输入时进行了一次自动更新,那么目前的输入信息会丢失,可能会导致一些后续问题。因此刷新不宜过分频繁。
这里的 EvalCmd
基本在调用之前实现的接口,并做了一些朴素的异常处理。
Demo
标签:std,itm,return,Process,void,int,Manager,Phase,include 来源: https://www.cnblogs.com/Shimarin/p/14623538.html