#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);
}
// 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;
}
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);
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;
// 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.
// 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 != "")
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(¤t_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 : */