]> sourceware.org Git - systemtap.git/blobdiff - cache.cxx
PR23879, PR24875: fix task-finder-vma on f29+
[systemtap.git] / cache.cxx
index 73dd59cec11b8606c9043a036a5aa937dcb1ce3d..3546b303742d8f230220ec5b72359be5d7d4ae04 100644 (file)
--- a/cache.cxx
+++ b/cache.cxx
 #include "session.h"
 #include "cache.h"
 #include "util.h"
-#include "sys/sdt.h"
+#include "stap-probe.h"
 #include <cerrno>
 #include <string>
 #include <fstream>
 #include <cstring>
 #include <cassert>
+#include <sstream>
+#include <vector>
 
 extern "C" {
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <glob.h>
+#include <regex.h>
+#include <utime.h>
+#include <sys/time.h>
+#include <unistd.h>
 }
 
 using namespace std;
 
 
 #define SYSTEMTAP_CACHE_MAX_FILENAME "cache_mb_limit"
-#define SYSTEMTAP_CACHE_DEFAULT_MB 64
+#define SYSTEMTAP_CACHE_DEFAULT_MB 256
+#define SYSTEMTAP_CACHE_CLEAN_INTERVAL_FILENAME "cache_clean_interval_s"
+#define SYSTEMTAP_CACHE_CLEAN_DEFAULT_INTERVAL_S 300
 
 struct cache_ent_info {
-  string path;
-  bool is_module;
-  size_t size;
-  long weight;  //lower == removed earlier
-
-  cache_ent_info(const string& path, bool is_module);
-  bool operator<(const struct cache_ent_info& other) const
-  { return weight < other.weight; }
+  vector<string> paths;
+  off_t size; // sum across all paths
+  time_t mtime; // newest of all paths
+
+  cache_ent_info(const vector<string>& paths);
+  bool operator<(const struct cache_ent_info& other) const;
   void unlink() const;
 };
 
-static void clean_cache(systemtap_session& s);
-
 
 void
-add_to_cache(systemtap_session& s)
+add_stapconf_to_cache(systemtap_session& s)
 {
-  // PR10543: clean the cache *before* we try putting something new into it.
-  // We don't want to risk having the brand new contents being erased again.
-  clean_cache(s);
+  bool verbose = s.verbose > 1;
 
   string stapconf_src_path = s.tmpdir + "/" + s.stapconf_name;
-  if (s.verbose > 1)
-    clog << "Copying " << stapconf_src_path << " to " << s.stapconf_path << endl;
-  if (copy_file(stapconf_src_path.c_str(), s.stapconf_path.c_str()) != 0)
+  if (!copy_file(stapconf_src_path, s.stapconf_path, verbose))
     {
-      cerr << "Copy failed (\"" << stapconf_src_path << "\" to \""
-          << s.stapconf_path << "\"): " << strerror(errno) << endl;
-      s.use_cache = false;
-      return;
+      // NB: this is not so severe as to prevent reuse of the .ko
+      // already copied.
+      //
+      // s.use_script_cache = false;
+      // return;
     }
+}
 
-  string module_src_path = s.tmpdir + "/" + s.module_name + ".ko";
-  STAP_PROBE2(stap, cache__add__module, module_src_path.c_str(), s.hash_path.c_str());
-  if (s.verbose > 1)
-    clog << "Copying " << module_src_path << " to " << s.hash_path << endl;
-  if (copy_file(module_src_path.c_str(), s.hash_path.c_str()) != 0)
+
+void
+add_script_to_cache(systemtap_session& s)
+{
+  bool verbose = s.verbose > 1;
+
+  // PR10543: clean the cache *before* we try putting something new into it.
+  // We don't want to risk having the brand new contents being erased again.
+  clean_cache(s);
+
+  string module_src_path = s.tmpdir + "/" + s.module_filename();
+  PROBE2(stap, cache__add__module, module_src_path.c_str(), s.hash_path.c_str());
+  if (!copy_file(module_src_path, s.hash_path, verbose))
     {
-      cerr << "Copy failed (\"" << module_src_path << "\" to \""
-          << s.hash_path << "\"): " << strerror(errno) << endl;
-      s.use_cache = false;
+      s.use_script_cache = false;
       return;
     }
+  // Copy the signature file, if any. It is not an error if this fails.
+  if (file_exists (module_src_path + ".sgn"))
+    copy_file(module_src_path + ".sgn", s.hash_path + ".sgn", verbose);
 
   string c_dest_path = s.hash_path;
-  if (c_dest_path.rfind(".ko") == (c_dest_path.size() - 3))
+  if (endswith(c_dest_path, ".ko") || endswith(c_dest_path, ".so"))
     c_dest_path.resize(c_dest_path.size() - 3);
   c_dest_path += ".c";
 
-  STAP_PROBE2(stap, cache__add__source, s.translated_source.c_str(), c_dest_path.c_str());
-  if (s.verbose > 1)
-    clog << "Copying " << s.translated_source << " to " << c_dest_path
-        << endl;
-  if (copy_file(s.translated_source.c_str(), c_dest_path.c_str()) != 0)
+  PROBE2(stap, cache__add__source, s.translated_source.c_str(), c_dest_path.c_str());
+  if (!copy_file(s.translated_source, c_dest_path, verbose))
     {
-      if (s.verbose > 1)
-        cerr << "Copy failed (\"" << s.translated_source << "\" to \""
-             << c_dest_path << "\"): " << strerror(errno) << endl;
       // NB: this is not so severe as to prevent reuse of the .ko
       // already copied.
       //
-      // s.use_cache = false;
+      // s.use_script_cache = false;
     }
 }
 
 
 bool
-get_from_cache(systemtap_session& s)
+get_stapconf_from_cache(systemtap_session& s)
 {
-  string stapconf_dest_path = s.tmpdir + "/" + s.stapconf_name;
-  string module_dest_path = s.tmpdir + "/" + s.module_name + ".ko";
-  string c_src_path = s.hash_path;
-  int fd_stapconf, fd_module, fd_c;
+  if (s.poison_cache)
+    return false;
 
-  if (c_src_path.rfind(".ko") == (c_src_path.size() - 3))
-    c_src_path.resize(c_src_path.size() - 3);
-  c_src_path += ".c";
+  string stapconf_dest_path = s.tmpdir + "/" + s.stapconf_name;
+  int fd_stapconf;
 
   // See if stapconf exists
   fd_stapconf = open(s.stapconf_path.c_str(), O_RDONLY);
@@ -118,10 +120,9 @@ get_from_cache(systemtap_session& s)
     }
 
   // Copy the stapconf header file to the destination
-  if (copy_file(s.stapconf_path.c_str(), stapconf_dest_path.c_str()) != 0)
+  if (!get_file_size(fd_stapconf) ||
+      !copy_file(s.stapconf_path, stapconf_dest_path))
     {
-      cerr << "Copy failed (\"" << s.stapconf_path << "\" to \""
-          << stapconf_dest_path << "\"): " << strerror(errno) << endl;
       close(fd_stapconf);
       return false;
     }
@@ -130,7 +131,25 @@ get_from_cache(systemtap_session& s)
   close(fd_stapconf);
 
   if (s.verbose > 1)
-    clog << "Pass 3: using cached " << s.stapconf_path << endl;
+    clog << _("Pass 4: using cached ") << s.stapconf_path << endl;
+
+  return true;
+}
+
+
+bool
+get_script_from_cache(systemtap_session& s)
+{
+  if (s.poison_cache)
+    return false;
+
+  string module_dest_path = s.tmpdir + "/" + s.module_filename();
+  string c_src_path = s.hash_path;
+  int fd_module, fd_c;
+
+  if (endswith(c_src_path, ".ko") || endswith(c_src_path, ".so"))
+    c_src_path.resize(c_src_path.size() - 3);
+  c_src_path += ".c";
 
   // See if module exists
   fd_module = open(s.hash_path.c_str(), O_RDONLY);
@@ -151,11 +170,11 @@ get_from_cache(systemtap_session& s)
       return false;
     }
 
-  // Copy the cached C file to the destination
-  if (copy_file(c_src_path.c_str(), s.translated_source.c_str()) != 0)
+  // Check that the files aren't empty, and then
+  // copy the cached C file to the destination
+  if (!get_file_size(fd_module) || !get_file_size(fd_c) ||
+      !copy_file(c_src_path, s.translated_source))
     {
-      cerr << "Copy failed (\"" << c_src_path << "\" to \""
-          << s.translated_source << "\"): " << strerror(errno) << endl;
       close(fd_module);
       close(fd_c);
       return false;
@@ -164,15 +183,17 @@ get_from_cache(systemtap_session& s)
   // Copy the cached module to the destination (if needed)
   if (s.last_pass != 3)
     {
-      if (copy_file(s.hash_path.c_str(), module_dest_path.c_str()) != 0)
+      if (!copy_file(s.hash_path, module_dest_path))
         {
-         cerr << "Copy failed (\"" << s.hash_path << "\" to \""
-              << module_dest_path << "\"): " << strerror(errno) << endl;
          unlink(c_src_path.c_str());
          close(fd_module);
          close(fd_c);
          return false;
        }
+      // Copy the module signature file, if any.
+      // It is not an error if this fails.
+      if (file_exists (s.hash_path + ".sgn"))
+       copy_file(s.hash_path + ".sgn", module_dest_path + ".sgn");
     }
 
   // We're done with these file handles.
@@ -194,20 +215,20 @@ get_from_cache(systemtap_session& s)
   // since if copying the cached C file works, but copying the cached
   // module fails, we remove the cached C file and let the C file get
   // regenerated.
-  if (s.verbose)
-    {
-      clog << "Pass 3: using cached " << c_src_path << endl;
-      if (s.last_pass != 3)
-       clog << "Pass 4: using cached " << s.hash_path << endl;
-    }
+  // NB: don't use s.verbose here, since we're still in pass-2,
+  // i.e., s.verbose = s.perpass_verbose[1].
+  if (s.perpass_verbose[2])
+    clog << _("Pass 3: using cached ") << c_src_path << endl;
+  if (s.perpass_verbose[3] && s.last_pass != 3)
+    clog << _("Pass 4: using cached ") << s.hash_path << endl;
 
-  STAP_PROBE2(stap, cache__get, c_src_path.c_str(), s.hash_path.c_str());
+  PROBE2(stap, cache__get, c_src_path.c_str(), s.hash_path.c_str());
 
   return true;
 }
 
 
-static void
+void
 clean_cache(systemtap_session& s)
 {
   if (s.cache_path != "")
@@ -226,157 +247,194 @@ clean_cache(systemtap_session& s)
       else
         {
           //file doesnt exist, create a default size
-         ofstream default_cache_max(cache_max_filename.c_str(), ios::out);
-         default_cache_max << SYSTEMTAP_CACHE_DEFAULT_MB << endl;
-         cache_mb_max = SYSTEMTAP_CACHE_DEFAULT_MB;
+          ofstream default_cache_max(cache_max_filename.c_str(), ios::out);
+          default_cache_max << SYSTEMTAP_CACHE_DEFAULT_MB << endl;
+          cache_mb_max = SYSTEMTAP_CACHE_DEFAULT_MB;
 
           if (s.verbose > 1)
-            clog << "Cache limit file " << s.cache_path << "/"
-              << SYSTEMTAP_CACHE_MAX_FILENAME
-              << " missing, creating default." << endl;
+            clog << _F("Cache limit file %s/%s missing, creating default.",
+                       s.cache_path.c_str(), SYSTEMTAP_CACHE_MAX_FILENAME) << endl;
         }
 
-      //glob for all kernel modules in the cache dir
-      glob_t cache_glob;
-      string glob_str = s.cache_path + "/*/*.ko";
-      glob(glob_str.c_str(), 0, NULL, &cache_glob);
+      /* Get cache clean interval from file in the stap cache dir */
+      string cache_clean_interval_filename = s.cache_path + "/";
+      cache_clean_interval_filename += SYSTEMTAP_CACHE_CLEAN_INTERVAL_FILENAME;
+      ifstream cache_clean_interval_file(cache_clean_interval_filename.c_str(), ios::in);
+      unsigned long cache_clean_interval;
 
+      if (cache_clean_interval_file.is_open())
+        {
+          cache_clean_interval_file >> cache_clean_interval;
+          cache_clean_interval_file.close();
+        }
+      else
+        {
+          //file doesnt exist, create a default interval
+          ofstream default_cache_clean_interval(cache_clean_interval_filename.c_str(), ios::out);
+          default_cache_clean_interval << SYSTEMTAP_CACHE_CLEAN_DEFAULT_INTERVAL_S << endl;
+          cache_clean_interval = SYSTEMTAP_CACHE_CLEAN_DEFAULT_INTERVAL_S;
 
-      set<struct cache_ent_info> cache_contents;
-      unsigned long cache_size_b = 0;
+          if (s.verbose > 1)
+            clog << _F("Cache clean interval file %s missing, creating default.",
+                       cache_clean_interval_filename.c_str())<< endl;
+        }
 
-      //grab info for each cache entry (.ko and .c)
-      for (unsigned int i = 0; i < cache_glob.gl_pathc; i++)
+      /* Check the cache cleaning interval */
+      struct stat sb;
+      if(stat(cache_clean_interval_filename.c_str(), &sb) < 0)
         {
-          string cache_ent_path = cache_glob.gl_pathv[i];
-          cache_ent_path.resize(cache_ent_path.length() - 3);
-
-          struct cache_ent_info cur_info(cache_ent_path, true);
-          if (cur_info.size != 0 && cur_info.weight != 0)
-            {
-              cache_size_b += cur_info.size;
-              cache_contents.insert(cur_info);
-            }
+          const char* e = strerror (errno);
+          cerr << _F("clean_cache stat error: %s", e) << endl;
+          return;
         }
 
-      globfree(&cache_glob);
-
-      //grab info for each typequery user module (.so)
-      glob_str = s.cache_path + "/*/*.so";
-      glob(glob_str.c_str(), 0, NULL, &cache_glob);
-      for (unsigned int i = 0; i < cache_glob.gl_pathc; i++)
+      struct timeval current_time;
+      gettimeofday(&current_time, NULL);
+      if(difftime(current_time.tv_sec, sb.st_mtime) < cache_clean_interval)
         {
-          string cache_ent_path = cache_glob.gl_pathv[i];
-          struct cache_ent_info cur_info(cache_ent_path, false);
-          if (cur_info.size != 0 && cur_info.weight != 0)
-            {
-              cache_size_b += cur_info.size;
-              cache_contents.insert(cur_info);
-            }
+          //interval not passed, don't continue
+          if (s.verbose > 1)
+            clog << _F("Cache cleaning skipped, interval not reached %lu s / %lu s.",
+                       (current_time.tv_sec-sb.st_mtime), cache_clean_interval)  << endl;
+          return;
+        }
+      else
+        {
+          //interval reached, continue
+          if (s.verbose > 1)
+            clog << _F("Cleaning cache, interval reached %lu s > %lu s.",
+                       (current_time.tv_sec-sb.st_mtime), cache_clean_interval)  << endl;
         }
 
+      // glob for all files that look like hashes
+      glob_t cache_glob;
+      ostringstream glob_pattern;
+      glob_pattern << s.cache_path << "/*/*";
+      for (unsigned int i = 0; i < 32; i++)
+        glob_pattern << "[[:xdigit:]]";
+      glob_pattern << "*";
+      int rc = glob(glob_pattern.str().c_str(), 0, NULL, &cache_glob);
+      if (rc) {
+        cerr << _F("clean_cache glob error rc=%d", rc) << endl;
+        return;
+      }
+
+      regex_t hash_len_re;
+      rc = regcomp (&hash_len_re, "([[:xdigit:]]{32}_[[:digit:]]+)", REG_EXTENDED);
+      if (rc) {
+        cerr << _F("clean_cache regcomp error rc=%d", rc) << endl;
+        globfree(&cache_glob);
+        return;
+      }
+
+      // group all files with the same HASH_LEN
+      map<string, vector<string> > cache_groups;
+      for (size_t i = 0; i < cache_glob.gl_pathc; i++)
+        {
+          const char* path = cache_glob.gl_pathv[i];
+          regmatch_t hash_len;
+          rc = regexec(&hash_len_re, path, 1, &hash_len, 0);
+          if (rc || hash_len.rm_so == -1 || hash_len.rm_eo == -1)
+            cache_groups[path].push_back(path); // ungrouped
+          else
+            cache_groups[string(path + hash_len.rm_so,
+                                hash_len.rm_eo - hash_len.rm_so)]
+              .push_back(path);
+        }
+      regfree(&hash_len_re);
       globfree(&cache_glob);
 
-      //grab info for each stapconf cache entry (.h)
-      glob_str = s.cache_path + "/*/*.h";
-      glob(glob_str.c_str(), 0, NULL, &cache_glob);
-      for (unsigned int i = 0; i < cache_glob.gl_pathc; i++)
+
+      // create each cache entry and accumulate the sum
+      off_t cache_size_b = 0;
+      set<cache_ent_info> cache_contents;
+      for (map<string, vector<string> >::const_iterator it = cache_groups.begin();
+           it != cache_groups.end(); ++it)
         {
-          string cache_ent_path = cache_glob.gl_pathv[i];
-          struct cache_ent_info cur_info(cache_ent_path, false);
-          if (cur_info.size != 0 && cur_info.weight != 0)
-            {
-              cache_size_b += cur_info.size;
-              cache_contents.insert(cur_info);
-            }
+          cache_ent_info cur_info(it->second);
+          if (cache_contents.insert(cur_info).second)
+            cache_size_b += cur_info.size;
         }
 
-      globfree(&cache_glob);
-
-      set<struct cache_ent_info>::iterator i;
       unsigned long r_cache_size = cache_size_b;
-      string removed_dirs = "";
+      vector<const cache_ent_info*> removed;
 
       //unlink .ko and .c until the cache size is under the limit
-      for (i = cache_contents.begin(); i != cache_contents.end(); ++i)
+      for (set<cache_ent_info>::iterator i = cache_contents.begin();
+           i != cache_contents.end(); ++i)
         {
-          if ( (r_cache_size / 1024 / 1024) < cache_mb_max)    //convert r_cache_size to MiB
+          if (r_cache_size < cache_mb_max * 1024 * 1024) //convert cache_mb_max to bytes
             break;
 
-          STAP_PROBE1(stap, cache__clean, (i->path).c_str());
           //remove this (*i) cache_entry, add to removed list
+          for (size_t j = 0; j < i->paths.size(); ++j)
+            PROBE1(stap, cache__clean, i->paths[j].c_str());
           i->unlink();
           r_cache_size -= i->size;
-          removed_dirs += i->path + ", ";
+          removed.push_back(&*i);
         }
 
-      cache_contents.clear();
+      if (s.verbose > 1 && !removed.empty())
+        {
+          clog << _("Cache cleaning successful, removed entries: ") << endl;
+          for (size_t i = 0; i < removed.size(); ++i)
+            for (size_t j = 0; j < removed[i]->paths.size(); ++j)
+              clog << "  " << removed[i]->paths[j] << endl;
+        }
 
-      if (s.verbose > 1 && removed_dirs != "")
+      if(utime(cache_clean_interval_filename.c_str(), NULL)<0)
         {
-          //remove trailing ", "
-          removed_dirs = removed_dirs.substr(0, removed_dirs.length() - 2);
-          clog << "Cache cleaning successful, removed entries: " 
-               << removed_dirs << endl;
+          const char* e = strerror (errno);
+          cerr << _F("clean_cache utime error: %s", e) << endl;
+          return;
         }
     }
   else
     {
       if (s.verbose > 1)
-        clog << "Cache cleaning skipped, no cache path." << endl;
+        clog << _("Cache cleaning skipped, no cache path.") << endl;
     }
 }
 
-//Assign a weight for a particular file. A lower weight
-// will be removed before a higher weight.
-//TODO: for now use system mtime... later base a
-// weighting on size, ctime, atime etc..
-static long
-get_file_weight(const string &path)
-{
-  time_t dir_mtime = 0;
-  struct stat dir_stat_info;
-
-  if (stat(path.c_str(), &dir_stat_info) == 0)
-    //GNU struct stat defines st_atime as st_atim.tv_sec
-    // but it doesnt seem to work properly in practice
-    // so use st_atim.tv_sec -- bad for portability?
-    dir_mtime = dir_stat_info.st_mtim.tv_sec;
 
-  return dir_mtime;
+cache_ent_info::cache_ent_info(const vector<string>& paths):
+  paths(paths), size(0), mtime(0)
+{
+  struct stat file_info;
+  for (size_t i = 0; i < paths.size(); ++i)
+    if (stat(paths[i].c_str(), &file_info) == 0)
+      {
+        size += file_info.st_size;
+        if (file_info.st_mtime > mtime)
+          mtime = file_info.st_mtime;
+      }
 }
 
 
-cache_ent_info::cache_ent_info(const string& path, bool is_module):
-  path(path), is_module(is_module)
+// The ordering here determines the order that
+// files will be removed from the cache.
+bool
+cache_ent_info::operator<(const struct cache_ent_info& other) const
 {
-  if (is_module)
-    {
-      string mod_path    = path + ".ko";
-      string source_path = path + ".c";
-      size = get_file_size(mod_path) + get_file_size(source_path);
-      weight = get_file_weight(mod_path);
-    }
-  else
-    {
-      size = get_file_size(path);
-      weight = get_file_weight(path);
-    }
+  if (mtime != other.mtime)
+    return mtime < other.mtime;
+  if (size != other.size)
+    return size < other.size;
+  if (paths.size() != other.paths.size())
+    return paths.size() < other.paths.size();
+  for (size_t i = 0; i < paths.size(); ++i)
+    if (paths[i] != other.paths[i])
+      return paths[i] < other.paths[i];
+  return false;
 }
 
 
 void
 cache_ent_info::unlink() const
 {
-  if (is_module)
-    {
-      string mod_path    = path + ".ko";
-      string source_path = path + ".c";
-      ::unlink(mod_path.c_str());
-      ::unlink(source_path.c_str());
-    }
-  else
-    ::unlink(path.c_str());
+  for (size_t i = 0; i < paths.size(); ++i)
+    ::unlink(paths[i].c_str());
 }
 
+
 /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */
This page took 0.033637 seconds and 5 git commands to generate.