Linux C/C++网络编程实战-陈硕-笔记23-procmon代码解析

陈鸿才
2023-12-01
#include "plot.h"

#include <muduo/base/FileUtil.h>
#include <muduo/base/ProcessInfo.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/http/HttpRequest.h>
#include <muduo/net/http/HttpResponse.h>
#include <muduo/net/http/HttpServer.h>

#include <boost/algorithm/string/replace.hpp>
#include <boost/bind.hpp>
#include <boost/circular_buffer.hpp>
#include <boost/type_traits/is_pod.hpp>

#include <sstream>
#include <stdarg.h>
#include <stdio.h>

using namespace muduo;
using namespace muduo::net;

// TODO:
// - what if process exits?
//

// Represent parsed /proc/pid/stat
struct StatData
{
  void parse(const char* startAtState, int kbPerPage)
  {
    // istringstream is probably not the most efficient way to parse it,
    // see muduo-protorpc/examples/collect/ProcFs.cc for alternatives.
    std::istringstream iss(startAtState);

    //            0    1    2    3     4    5       6   7 8 9  11  13   15
    // 3770 (cat) R 3718 3770 3718 34818 3770 4202496 214 0 0 0 0 0 0 0 20
    // 16  18     19      20 21                   22      23      24              25
    //  0 1 0 298215 5750784 81 18446744073709551615 4194304 4242836 140736345340592
    //              26
    // 140736066274232 140575670169216 0 0 0 0 0 0 0 17 0 0 0 0 0 0

    iss >> state;
    iss >> ppid >> pgrp >> session >> tty_nr >> tpgid >> flags;
    iss >> minflt >> cminflt >> majflt >> cmajflt;
    iss >> utime >> stime >> cutime >> cstime;
    iss >> priority >> nice >> num_threads >> itrealvalue >> starttime;
    long vsize, rss;
    iss >> vsize >> rss >> rsslim;
    vsizeKb = vsize / 1024;
    rssKb = rss * kbPerPage;
  }
  // int pid;
  char state;
  int ppid;
  int pgrp;
  int session;
  int tty_nr;
  int tpgid;
  int flags;

  long minflt;
  long cminflt;
  long majflt;
  long cmajflt;

  long utime;
  long stime;
  long cutime;
  long cstime;

  long priority;
  long nice;
  long num_threads;
  long itrealvalue;
  long starttime;

  long vsizeKb;
  long rssKb;
  long rsslim;
};

BOOST_STATIC_ASSERT(boost::is_pod<StatData>::value);

class Procmon : boost::noncopyable
{
 public:
  Procmon(EventLoop* loop, pid_t pid, uint16_t port, const char* procname)
    : kClockTicksPerSecond_(muduo::ProcessInfo::clockTicksPerSecond()),
      kbPerPage_(muduo::ProcessInfo::pageSize() / 1024),
      kBootTime_(getBootTime()),
      pid_(pid),
      server_(loop, InetAddress(port), getName()),
      procname_(ProcessInfo::procname(readProcFile("stat")).as_string()),
      hostname_(ProcessInfo::hostname()),
      cmdline_(getCmdLine()),
      ticks_(0),
      cpu_usage_(600 / kPeriod_),  // 10 minutes
      cpu_chart_(640, 100, 600, kPeriod_),
      ram_chart_(640, 100, 7200, 30)
  {
    bzero(&lastStatData_, sizeof lastStatData_);
    server_.setHttpCallback(boost::bind(&Procmon::onRequest, this, _1, _2));
  }

  void start()
  {
    tick();
    server_.getLoop()->runEvery(kPeriod_, boost::bind(&Procmon::tick, this));
    server_.start();
  }

 private:

  string getName() const
  {
    char name[256];
    snprintf(name, sizeof name, "procmon-%d", pid_);
    return name;
  }

  void onRequest(const HttpRequest& req, HttpResponse* resp)
  {
    resp->setStatusCode(HttpResponse::k200Ok);
    resp->setStatusMessage("OK");
    resp->setContentType("text/plain");
    resp->addHeader("Server", "Muduo-Procmon");

    /*
    if (!processExists(pid_))
    {
      resp->setStatusCode(HttpResponse::k404NotFound);
      resp->setStatusMessage("Not Found");
      resp->setCloseConnection(true);
      return;
    }
    */

    if (req.path() == "/")
    {
      resp->setContentType("text/html");
      fillOverview(req.query());
      resp->setBody(response_.retrieveAllAsString());
    }
    else if (req.path() == "/cmdline")
    {
      resp->setBody(cmdline_);
    }
    else if (req.path() == "/cpu.png")
    {
      std::vector<double> cpu_usage;
      for (size_t i = 0; i < cpu_usage_.size(); ++i)
        cpu_usage.push_back(cpu_usage_[i].cpuUsage(kPeriod_, kClockTicksPerSecond_));
      string png = cpu_chart_.plotCpu(cpu_usage);
      resp->setContentType("image/png");
      resp->setBody(png);
    }
    // FIXME: replace with a map
    else if (req.path() == "/environ")
    {
      resp->setBody(getEnviron());
    }
    else if (req.path() == "/io")
    {
      resp->setBody(readProcFile("io"));
    }
    else if (req.path() == "/limits")
    {
      resp->setBody(readProcFile("limits"));
    }
    else if (req.path() == "/maps")
    {
      resp->setBody(readProcFile("maps"));
    }
    // numa_maps
    else if (req.path() == "/smaps")
    {
      resp->setBody(readProcFile("smaps"));
    }
    else if (req.path() == "/status")
    {
      resp->setBody(readProcFile("status"));
    }
    else if (req.path() == "/threads")
    {
      fillThreads();
      resp->setBody(response_.retrieveAllAsString());
    }
    else
    {
      resp->setStatusCode(HttpResponse::k404NotFound);
      resp->setStatusMessage("Not Found");
      resp->setCloseConnection(true);
    }
  }

  void fillOverview(const string& query)
  {
    response_.retrieveAll();
    Timestamp now = Timestamp::now();
    appendResponse("<html><head><title>%s on %s</title>\n",
                   procname_.c_str(), hostname_.c_str());
    fillRefresh(query);
    appendResponse("</head><body>\n");

    string stat = readProcFile("stat");
    if (stat.empty())
    {
      appendResponse("<h1>PID %d doesn't exist.</h1></body></html>", pid_);
      return;
    }
    int pid = atoi(stat.c_str());
    assert(pid == pid_);
    StringPiece procname = ProcessInfo::procname(stat);
    appendResponse("<h1>%s on %s</h1>\n",
                   procname.as_string().c_str(), hostname_.c_str());
    response_.append("<p>Refresh <a href=\"?refresh=1\">1s</a> ");
    response_.append("<a href=\"?refresh=2\">2s</a> ");
    response_.append("<a href=\"?refresh=5\">5s</a> ");
    response_.append("<a href=\"?refresh=15\">15s</a> ");
    response_.append("<a href=\"?refresh=60\">60s</a>\n");
    response_.append("<p><a href=\"/cmdline\">Command line</a>\n");
    response_.append("<a href=\"/environ\">Environment variables</a>\n");
    response_.append("<a href=\"/threads\">Threads</a>\n");

    appendResponse("<p>Page generated at %s (UTC)", now.toFormattedString().c_str());

    response_.append("<p><table>");
    StatData statData;  // how about use lastStatData_ ?
    bzero(&statData, sizeof statData);
    statData.parse(procname.end()+1, kbPerPage_);  // end is ')'

    appendTableRow("PID", pid);
    Timestamp started(getStartTime(statData.starttime));  // FIXME: cache it;
    appendTableRow("Started at", started.toFormattedString(false /*showMicroseconds*/) + " (UTC)");
    appendTableRowFloat("Uptime (s)", timeDifference(now, started));  // FIXME: format as days+H:M:S
    appendTableRow("Executable", readLink("exe"));
    appendTableRow("Current dir", readLink("cwd"));

    appendTableRow("State", getState(statData.state));
    appendTableRowFloat("User time (s)", getSeconds(statData.utime));
    appendTableRowFloat("System time (s)", getSeconds(statData.stime));

    appendTableRow("VmSize (KiB)", statData.vsizeKb);
    appendTableRow("VmRSS (KiB)", statData.rssKb);
    appendTableRow("Threads", statData.num_threads);
    appendTableRow("CPU usage", "<img src=\"/cpu.png\" height=\"100\" witdh=\"640\">");

    appendTableRow("Priority", statData.priority);
    appendTableRow("Nice", statData.nice);

    appendTableRow("Minor page faults", statData.minflt);
    appendTableRow("Major page faults", statData.majflt);
    // TODO: user

    response_.append("</table>");
    response_.append("</body></html>");
  }

  void fillRefresh(const string& query)
  {
    size_t p = query.find("refresh=");
    if (p != string::npos)
    {
      int seconds = atoi(query.c_str()+p+8);
      if (seconds > 0)
      {
        appendResponse("<meta http-equiv=\"refresh\" content=\"%d\">\n", seconds);
      }
    }
  }

  void fillThreads()
  {
    response_.retrieveAll();
    // FIXME: implement this
  }

  string readProcFile(const char* basename)
  {
    char filename[256];
    snprintf(filename, sizeof filename, "/proc/%d/%s", pid_, basename);
    string content;
    FileUtil::readFile(filename, 1024*1024, &content);
    return content;
  }

  string readLink(const char* basename)
  {
    char filename[256];
    snprintf(filename, sizeof filename, "/proc/%d/%s", pid_, basename);
    char link[1024];
    ssize_t len = ::readlink(filename, link, sizeof link);
    string result;
    if (len > 0)
    {
      result.assign(link, len);
    }
    return result;
  }

  int appendResponse(const char* fmt, ...) __attribute__ ((format (printf, 2, 3)));

  void appendTableRow(const char* name, long value)
  {
    appendResponse("<tr><td>%s</td><td>%ld</td></tr>\n", name, value);
  }

  void appendTableRowFloat(const char* name, double value)
  {
    appendResponse("<tr><td>%s</td><td>%.2f</td></tr>\n", name, value);
  }

  void appendTableRow(const char* name, StringArg value)
  {
    appendResponse("<tr><td>%s</td><td>%s</td></tr>\n", name, value.c_str());
  }

  string getCmdLine()
  {
    return boost::replace_all_copy(readProcFile("cmdline"), string(1, '\0'), "\n\t");
  }

  string getEnviron()
  {
    return boost::replace_all_copy(readProcFile("environ"), string(1, '\0'), "\n");
  }

  Timestamp getStartTime(long starttime)
  {
    return Timestamp(Timestamp::kMicroSecondsPerSecond * kBootTime_
                     + Timestamp::kMicroSecondsPerSecond * starttime / kClockTicksPerSecond_);
  }

  double getSeconds(long ticks)
  {
    return static_cast<double>(ticks) / kClockTicksPerSecond_;
  }

  void tick()
  {
    string stat = readProcFile("stat");  // FIXME: catch file descriptor
    if (stat.empty())
      return;
    StringPiece procname = ProcessInfo::procname(stat);
    StatData statData;
    bzero(&statData, sizeof statData);
    statData.parse(procname.end()+1, kbPerPage_);  // end is ')'
    if (ticks_ > 0)
    {
      CpuTime time;
      time.userTime_ = std::max(0, static_cast<int>(statData.utime - lastStatData_.utime));
      time.sysTime_ = std::max(0, static_cast<int>(statData.stime - lastStatData_.stime));
      cpu_usage_.push_back(time);
    }

    lastStatData_ = statData;
    ++ticks_;
  }

  //
  // static member functions
  //

  static const char* getState(char state)
  {
    // One  character  from  the  string  "RSDZTW"  where R is running, S is sleeping in an
    // interruptible wait, D is waiting in uninterruptible disk sleep, Z is  zombie,  T  is
    // traced or stopped (on a signal), and W is paging.
    switch (state)
    {
      case 'R':
        return "Running";
      case 'S':
        return "Sleeping";
      case 'D':
        return "Disk sleep";
      case 'Z':
        return "Zombie";
      default:
        return "Unknown";
    }
  }

  static long getLong(const string& status, const char* key)
  {
    long result = 0;
    size_t pos = status.find(key);
    if (pos != string::npos)
    {
      result = ::atol(status.c_str() + pos + strlen(key));
    }
    return result;
  }

  static long getBootTime()
  {
    string stat;
    FileUtil::readFile("/proc/stat", 65536, &stat);
    return getLong(stat, "btime ");
  }

  struct CpuTime
  {
    int userTime_;
    int sysTime_;
    double cpuUsage(double kPeriod, double kClockTicksPerSecond) const
    {
      return (userTime_ + sysTime_) / (kClockTicksPerSecond * kPeriod);
    }
  };

  const static int kPeriod_ = 2.0;
  const int kClockTicksPerSecond_;
  const int kbPerPage_;
  const long kBootTime_;  // in Unix-time
  const pid_t pid_;
  HttpServer server_;
  const string procname_;
  const string hostname_;
  const string cmdline_;
  int ticks_;
  StatData lastStatData_;
  boost::circular_buffer<CpuTime> cpu_usage_;
  Plot cpu_chart_;
  Plot ram_chart_;
  // scratch variables
  Buffer response_;
};

// define outline for __attribute__
int Procmon::appendResponse(const char* fmt, ...)
{
  char buf[1024];
  va_list args;
  va_start(args, fmt);
  int ret = vsnprintf(buf, sizeof buf, fmt, args);
  va_end(args);
  response_.append(buf);
  return ret;
}

bool processExists(pid_t pid)
{
  char filename[256];
  snprintf(filename, sizeof filename, "/proc/%d/stat", pid);
  return ::access(filename, R_OK) == 0;
}

int main(int argc, char* argv[])
{
  if (argc < 3)
  {
    printf("Usage: %s pid port [name]\n", argv[0]);
    return 0;
  }
  int pid = atoi(argv[1]);
  if (!processExists(pid))
  {
    printf("Process %d doesn't exist.\n", pid);
    return 1;
  }

  EventLoop loop;
  uint16_t port = static_cast<uint16_t>(atoi(argv[2]));
  Procmon procmon(&loop, pid, port, argc > 3 ? argv[3] : "");
  procmon.start();
  loop.loop();
}
 类似资料: