BPicture: Fix archive constructor.
[haiku.git] / src / add-ons / kernel / file_cache / rule_based_prefetcher.cpp
blob322359fba4edc409ae91d765fe659041b2de18aa
1 /*
2 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
6 /** This module applies rules that were learned by another component by
7 * evaluating the cache log files.
9 * Note: this module is using private kernel API and is definitely not
10 * meant to be an example on how to write modules.
14 #include <KernelExport.h>
15 #include <Node.h>
17 #include <util/kernel_cpp.h>
18 #include <util/AutoLock.h>
19 #include <thread.h>
20 #include <team.h>
21 #include <file_cache.h>
22 #include <generic_syscall.h>
23 #include <syscalls.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <ctype.h>
33 #define TRACE_CACHE_MODULE
34 #ifdef TRACE_CACHE_MODULE
35 # define TRACE(x) dprintf x
36 #else
37 # define TRACE(x) ;
38 #endif
41 extern dev_t gBootDevice;
44 enum match_type {
45 NO_MATCH,
46 CONTINUE_MATCHING,
47 MATCH,
50 enum rule_type {
51 LAUNCH_TYPE = 0x1,
52 OPEN_FILE_TYPE = 0x2,
53 ARGUMENTS_TYPE = 0x4,
54 ALL_TYPES = 0xff,
57 struct head {
58 head();
60 struct list_link link;
61 mount_id device;
62 vnode_id parent;
63 vnode_id node;
64 char name[B_FILE_NAME_LENGTH];
65 int32 confidence;
66 bigtime_t timestamp;
67 int32 used_count;
70 struct body {
71 struct list_link link;
74 class Rule {
75 public:
76 Rule(rule_type type);
77 ~Rule();
79 status_t InitCheck();
80 rule_type Type() const { return fType; }
82 void AddHead(struct head *head);
83 void AddBody(struct body *body);
85 struct head *FindHead(mount_id device, vnode_id node);
86 match_type Match(int32 state, mount_id device, vnode_id parent,
87 const char *name);
88 void Apply();
90 void KnownFileOpened() { fKnownFileOpened++; }
91 void UnknownFileOpened() { fUnknownFileOpened++; }
93 void Dump();
95 Rule *&Next() { return fNext; }
97 static uint32 NextOffset() { return offsetof(Rule, fNext); }
98 static status_t LoadRules();
100 private:
101 Rule *fNext;
102 rule_type fType;
103 struct list fHeads;
104 struct list fBodies;
105 int32 fHeadCount;
106 int32 fBodyCount;
107 int32 fConfidence;
108 int32 fAppliedCount;
109 int32 fKnownFileOpened;
110 int32 fUnknownFileOpened;
113 struct rule_state {
114 list_link link;
115 Rule *rule;
116 int32 state;
119 struct rules {
120 rules *next;
121 char name[B_FILE_NAME_LENGTH];
122 Rule *first;
125 struct team_rules {
126 ~team_rules();
128 team_rules *next;
129 team_id team;
130 struct list states;
131 struct list applied;
132 bigtime_t timestamp;
135 class RuleMatcher {
136 public:
137 RuleMatcher(team_id team, const char *name = NULL);
138 ~RuleMatcher();
140 void GotFile(mount_id device, vnode_id node);
141 void GotArguments(int32 argCount, char * const *args);
143 private:
144 void _CollectRules(const char *name);
145 void _MatchFile(mount_id device, vnode_id parent, const char *name);
146 void _MatchArguments(int32 argCount, char * const *args);
148 team_rules *fRules;
152 struct RuleHash {
153 typedef char* KeyType;
154 typedef rules ValueType;
156 size_t HashKey(KeyType key) const
158 return hash_hash_string(key);
161 size_t Hash(ValueType* value) const
163 return HashKey(value->name);
166 bool Compare(KeyType key, ValueType* rules) const
168 return strcmp(rules->name, key) == 0;
171 ValueType*& GetLink(ValueType* value) const
173 return value->fNext;
178 struct TeamHash {
179 typedef team_id KeyType;
180 typedef team_rules ValueType;
182 size_t HashKey(KeyType key) const
184 return key;
187 size_t Hash(ValueType* value) const
189 return value->team;
192 bool Compare(KeyType key, ValueType* value) const
194 return value->team == key;
197 ValueType*& GetLink(ValueType* value) const
199 return value->fNext;
204 typedef BOpenHashTable<RuleHash> RuleTable;
205 typedef BOpenHashTable<TeamHash> TeamTable;
207 static RuleTable *sRulesHash;
208 static TeamTable *sTeamHash;
209 static recursive_lock sLock;
210 int32 sMinConfidence = 5000;
213 static void
214 team_gone(team_id team, void *_rules)
216 team_rules *rules = (team_rules *)_rules;
218 recursive_lock_lock(&sLock);
219 sTeamHash->Remove(rules);
220 recursive_lock_unlock(&sLock);
222 delete rules;
226 team_rules::~team_rules()
228 // free rule states
230 rule_state *state;
231 while ((state = (rule_state *)list_remove_head_item(&states)) != NULL) {
232 delete state;
234 while ((state = (rule_state *)list_remove_head_item(&applied)) != NULL) {
235 delete state;
238 stop_watching_team(team, team_gone, this);
242 // #pragma mark -
245 head::head()
247 device = -1;
248 parent = -1;
249 node = -1;
250 name[0] = '\0';
251 confidence = -1;
252 timestamp = 0;
253 used_count = 0;
257 static inline rules *
258 find_rules(const char *name)
260 return sRulesHash->Lookup(name);
264 /** Finds the rule matching to the criteria (name and type).
265 * If there is no matching rule yet, it will create one.
266 * It will only return NULL in out of memory conditions.
269 static Rule *
270 get_rule(const char *name, rule_type type)
272 struct rules *rules = find_rules(name);
273 if (rules == NULL) {
274 // add new rules
275 rules = new ::rules;
276 if (rules == NULL)
277 return NULL;
279 strlcpy(rules->name, name, B_FILE_NAME_LENGTH);
280 rules->first = NULL;
281 sRulesHash->Insert(rules);
284 // search for matching rule type
286 Rule *rule = rules->first;
287 while (rule && rule->Type() != type) {
288 rule = rule->Next();
291 if (rule == NULL) {
292 TRACE(("create new rule for \"%s\", type %d\n", name, type));
293 // there is no rule yet, create one
294 rule = new Rule(type);
295 if (rule == NULL)
296 return NULL;
298 rule->Next() = rules->first;
299 rules->first = rule;
302 return rule;
306 static void
307 eat_spaces(char *&line)
309 // eat starting white space
310 while (isspace(line[0]))
311 line++;
315 static bool
316 parse_ref(const char *string, mount_id &device, vnode_id &node, char **_end = NULL)
318 // parse node ref
319 char *end;
320 device = strtol(string, &end, 0);
321 if (end == NULL || device == 0 || end[0] != ':')
322 return false;
324 node = strtoull(end + 1, &end, 0);
325 if (end == NULL || end[0] != ':')
326 return false;
328 if (_end)
329 *_end = end + 1;
330 return true;
334 static void
335 ignore_line(char *&line)
337 while (line[0] && line[0] != '\n')
338 line++;
342 static const char *
343 get_name(char *&line)
345 if (line[0] != '"')
346 return NULL;
348 const char *name = ++line;
350 while (line[0] && line[0] != '"') {
351 if (line[0] == '\\')
352 line++;
353 line++;
356 line[0] = '\0';
357 line++;
359 return name;
363 static status_t
364 load_rules()
366 int fd = open("/etc/cache_rules", O_RDONLY);
367 if (fd < B_OK)
368 return fd;
370 struct stat stat;
371 if (fstat(fd, &stat) != 0) {
372 close(fd);
373 return errno;
376 if (stat.st_size > 32767) {
377 // for safety reasons
378 // ToDo: make a bit larger later
379 close(fd);
380 return B_BAD_DATA;
383 char *buffer = (char *)malloc(stat.st_size + 1);
384 if (buffer == NULL) {
385 close(fd);
386 return B_NO_MEMORY;
389 if (read(fd, buffer, stat.st_size) < stat.st_size) {
390 free(buffer);
391 close(fd);
392 return B_ERROR;
395 buffer[stat.st_size] = '\0';
397 char *line = buffer;
398 while (line[0]) {
399 switch (line[0]) {
400 case 'c':
402 mount_id device;
403 vnode_id node;
405 // direct "command opens file" rule
406 line += 2;
407 if (parse_ref(line, device, node, &line)) {
408 const char *fileName = get_name(line);
409 if (fileName != NULL) {
410 eat_spaces(line);
411 const char *name = get_name(line);
412 if (name != NULL) {
413 eat_spaces(line);
414 int32 confidence = strtoul(line, &line, 10);
415 TRACE(("c %ld:%Ld:%s %s %ld\n", device, node, fileName, name, confidence));
417 struct head *head = new ::head;
418 head->device = device;
419 head->parent = node;
420 strlcpy(head->name, fileName, B_FILE_NAME_LENGTH);
421 head->confidence = confidence;
423 Rule *rule = get_rule(name, LAUNCH_TYPE);
424 if (rule != NULL)
425 rule->AddHead(head);
426 break;
430 ignore_line(line);
431 break;
433 default:
434 // unknown rule - ignore line
435 ignore_line(line);
436 break;
438 line++;
441 free(buffer);
442 close(fd);
443 return B_OK;
447 // #pragma mark -
450 Rule::Rule(rule_type type)
452 fType(type),
453 fHeadCount(0),
454 fBodyCount(0),
455 fAppliedCount(0),
456 fKnownFileOpened(0),
457 fUnknownFileOpened(0)
459 list_init(&fHeads);
460 list_init(&fBodies);
464 Rule::~Rule()
466 struct head *head;
467 while ((head = (struct head *)list_remove_head_item(&fHeads)) != NULL) {
468 delete head;
471 struct body *body;
472 while ((body = (struct body *)list_remove_head_item(&fBodies)) != NULL) {
473 delete body;
478 void
479 Rule::AddHead(struct head *head)
481 list_add_item(&fHeads, head);
482 fHeadCount++;
486 void
487 Rule::AddBody(struct body *body)
489 list_add_item(&fBodies, body);
490 fBodyCount++;
494 void
495 Rule::Apply()
497 TRACE(("Apply rule %p", this));
499 struct head *head = NULL;
500 while ((head = (struct head *)list_get_next_item(&fHeads, head)) != NULL) {
501 if (head->confidence < sMinConfidence)
502 continue;
504 // prefetch node
505 void *vnode;
506 if (vfs_entry_ref_to_vnode(head->device, head->parent, head->name, &vnode) == B_OK) {
507 vfs_vnode_to_node_ref(vnode, &head->device, &head->node);
509 TRACE(("prefetch: %ld:%Ld:%s\n", head->device, head->parent, head->name));
510 cache_prefetch(head->device, head->node, 0, ~0UL);
512 // ToDo: put head into a hash so that some statistics can be backpropagated quickly
513 } else {
514 // node doesn't exist anymore
515 head->confidence = -1;
519 fAppliedCount++;
523 struct head *
524 Rule::FindHead(mount_id device, vnode_id node)
526 // ToDo: use a hash for this!
528 struct head *head = NULL;
529 while ((head = (struct head *)list_get_next_item(&fHeads, head)) != NULL) {
530 if (head->node == node && head->device == device)
531 return head;
534 return NULL;
538 void
539 Rule::Dump()
541 dprintf(" applied: %ld, known: %ld, unknown: %ld\n",
542 fAppliedCount, fKnownFileOpened, fUnknownFileOpened);
544 struct head *head = NULL;
545 while ((head = (struct head *)list_get_next_item(&fHeads, head)) != NULL) {
546 dprintf(" %ld:%Ld:\"%s\", ", head->device, head->parent, head->name);
547 if (head->confidence < sMinConfidence)
548 dprintf("-\n");
549 else
550 dprintf("%ld (%ld), %Ld us\n", head->used_count,
551 head->used_count - fAppliedCount, head->timestamp);
556 // #pragma mark -
559 RuleMatcher::RuleMatcher(team_id team, const char *name)
561 fRules(NULL)
563 recursive_lock_lock(&sLock);
565 if (name == NULL)
566 return;
568 fRules = sTeamHash->Lookup(team);
569 if (fRules != NULL)
570 return;
572 fRules = new team_rules;
573 if (fRules == NULL)
574 return;
576 fRules->team = team;
577 list_init(&fRules->states);
578 list_init(&fRules->applied);
580 dprintf("new rules for \"%s\"\n", name);
581 _CollectRules(name);
583 sTeamHash->Insert(fRules);
584 start_watching_team(team, team_gone, fRules);
586 fRules->timestamp = system_time();
590 RuleMatcher::~RuleMatcher()
592 recursive_lock_unlock(&sLock);
596 void
597 RuleMatcher::_CollectRules(const char *name)
599 struct rules *rules = sRulesHash->Lookup(name);
600 if (rules == NULL) {
601 // there are no rules for this command
602 return;
605 // allocate states for all rules found
607 for (Rule *rule = rules->first; rule != NULL; rule = rule->Next()) {
608 rule_state *state = new rule_state;
609 if (state == NULL)
610 continue;
612 TRACE(("found rule %p for \"%s\"\n", rule, rules->name));
613 state->rule = rule;
614 state->state = 0;
616 if (rule->Type() == LAUNCH_TYPE) {
617 // we can already prefetch the simplest of all rules here
618 // (it's fulfilled as soon as the command is launched)
619 rule->Apply();
620 list_add_item(&fRules->applied, state);
621 } else
622 list_add_item(&fRules->states, state);
627 void
628 RuleMatcher::GotFile(mount_id device, vnode_id node)
630 if (fRules == NULL)
631 return;
633 // try to match the file with all open rules
635 rule_state *state = NULL;
636 while ((state = (rule_state *)list_get_next_item(&fRules->states, state)) != NULL) {
637 if ((state->rule->Type() & OPEN_FILE_TYPE) == 0)
638 continue;
640 // ToDo
643 bigtime_t diff = system_time() - fRules->timestamp;
645 // propagate the usage of this file back to the applied rules
647 while ((state = (rule_state *)list_get_next_item(&fRules->applied, state)) != NULL) {
648 struct head *head = state->rule->FindHead(device, node);
649 if (head != NULL) {
650 // grow confidence
651 state->rule->KnownFileOpened();
652 head->used_count++;
653 if (head->used_count > 1)
654 head->timestamp = (head->timestamp * (head->used_count - 1) + diff) / head->used_count;
655 } else
656 state->rule->UnknownFileOpened();
661 void
662 RuleMatcher::GotArguments(int32 argCount, char * const *args)
664 if (fRules == NULL)
665 return;
667 // try to match the arguments with all open rules
669 rule_state *state = NULL;
670 while ((state = (rule_state *)list_get_next_item(&fRules->states, state)) != NULL) {
671 if ((state->rule->Type() & ARGUMENTS_TYPE) == 0)
672 continue;
674 // ToDo
679 // #pragma mark -
682 static void
683 node_opened(void *vnode, int32 fdType, mount_id device, vnode_id parent,
684 vnode_id node, const char *name, off_t size)
686 if (device < gBootDevice) {
687 // we ignore any access to rootfs, pipefs, and devfs
688 // ToDo: if we can ever move the boot device on the fly, this will break
689 return;
692 // ToDo: this is only needed if there is no rule for this team yet - ideally,
693 // it would be handled by the log module, so that the vnode ID is sufficient
694 // to recognize a rule
695 char buffer[B_FILE_NAME_LENGTH];
696 if (name == NULL
697 && vfs_get_vnode_name(vnode, buffer, sizeof(buffer)) == B_OK)
698 name = buffer;
700 //dprintf("opened: %ld:%Ld:%Ld:%s (%s)\n", device, parent, node, name, thread_get_current_thread()->name);
701 RuleMatcher matcher(team_get_current_team_id(), name);
702 matcher.GotFile(device, node);
706 static void
707 node_launched(size_t argCount, char * const *args)
709 //dprintf("launched: %s (%s)\n", args[0], thread_get_current_thread()->name);
710 RuleMatcher matcher(team_get_current_team_id());
711 matcher.GotArguments(argCount, args);
715 static void
716 uninit()
718 recursive_lock_lock(&sLock);
720 // free all sessions from the hashes
722 team_rules *teamRules = sTeamHash->Clear(true);
723 while ((teamRules != NULL) {
724 team_rules *next = teamRules->next;
725 delete teamRules;
726 teamRules = next;
729 struct rules *rules = sRulesHash->Clear(true);
730 while ((rules != NULL) {
731 Rule *rule = rules->first;
732 while (rule) {
733 Rule *next = rule->Next();
735 dprintf("Rule %p \"%s\"\n", rule, rules->name);
736 rule->Dump();
738 delete rule;
739 rule = next;
742 struct rules *next = rules->next;
743 delete rules;
744 rules = next;
747 delete sTeamHash;
748 delete sRulesHash;
749 recursive_lock_destroy(&sLock);
753 static status_t
754 init()
756 sTeamHash = new(std::nothrow) TeamTable();
757 if (sTeamHash == NULL || sTeamHash->Init(64) != B_OK)
758 return B_NO_MEMORY;
760 status_t status;
762 sRulesHash = new(std::nothrow) RuleTable();
763 if (sRulesHash == NULL || sRulesHash->Init(64) != B_OK) {
764 delete sTeamHash;
765 return B_NO_MEMORY;
768 recursive_lock_init(&sLock, "rule based prefetcher");
770 load_rules();
771 return B_OK;
775 static status_t
776 std_ops(int32 op, ...)
778 switch (op) {
779 case B_MODULE_INIT:
780 return init();
782 case B_MODULE_UNINIT:
783 uninit();
784 return B_OK;
786 default:
787 return B_ERROR;
792 static struct cache_module_info sRuleBasedPrefetcherModule = {
794 CACHE_MODULES_NAME "/rule_based_prefetcher/v1",
796 std_ops,
798 node_opened,
799 NULL,
800 node_launched,
804 module_info *modules[] = {
805 (module_info *)&sRuleBasedPrefetcherModule,
806 NULL