2 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
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>
17 #include <util/kernel_cpp.h>
18 #include <util/AutoLock.h>
21 #include <file_cache.h>
22 #include <generic_syscall.h>
33 #define TRACE_CACHE_MODULE
34 #ifdef TRACE_CACHE_MODULE
35 # define TRACE(x) dprintf x
41 extern dev_t gBootDevice
;
60 struct list_link link
;
64 char name
[B_FILE_NAME_LENGTH
];
71 struct list_link link
;
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
,
90 void KnownFileOpened() { fKnownFileOpened
++; }
91 void UnknownFileOpened() { fUnknownFileOpened
++; }
95 Rule
*&Next() { return fNext
; }
97 static uint32
NextOffset() { return offsetof(Rule
, fNext
); }
98 static status_t
LoadRules();
109 int32 fKnownFileOpened
;
110 int32 fUnknownFileOpened
;
121 char name
[B_FILE_NAME_LENGTH
];
137 RuleMatcher(team_id team
, const char *name
= NULL
);
140 void GotFile(mount_id device
, vnode_id node
);
141 void GotArguments(int32 argCount
, char * const *args
);
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
);
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
179 typedef team_id KeyType
;
180 typedef team_rules ValueType
;
182 size_t HashKey(KeyType key
) const
187 size_t Hash(ValueType
* value
) const
192 bool Compare(KeyType key
, ValueType
* value
) const
194 return value
->team
== key
;
197 ValueType
*& GetLink(ValueType
* value
) const
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;
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
);
226 team_rules::~team_rules()
231 while ((state
= (rule_state
*)list_remove_head_item(&states
)) != NULL
) {
234 while ((state
= (rule_state
*)list_remove_head_item(&applied
)) != NULL
) {
238 stop_watching_team(team
, team_gone
, this);
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.
270 get_rule(const char *name
, rule_type type
)
272 struct rules
*rules
= find_rules(name
);
279 strlcpy(rules
->name
, name
, B_FILE_NAME_LENGTH
);
281 sRulesHash
->Insert(rules
);
284 // search for matching rule type
286 Rule
*rule
= rules
->first
;
287 while (rule
&& rule
->Type() != type
) {
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
);
298 rule
->Next() = rules
->first
;
307 eat_spaces(char *&line
)
309 // eat starting white space
310 while (isspace(line
[0]))
316 parse_ref(const char *string
, mount_id
&device
, vnode_id
&node
, char **_end
= NULL
)
320 device
= strtol(string
, &end
, 0);
321 if (end
== NULL
|| device
== 0 || end
[0] != ':')
324 node
= strtoull(end
+ 1, &end
, 0);
325 if (end
== NULL
|| end
[0] != ':')
335 ignore_line(char *&line
)
337 while (line
[0] && line
[0] != '\n')
343 get_name(char *&line
)
348 const char *name
= ++line
;
350 while (line
[0] && line
[0] != '"') {
366 int fd
= open("/etc/cache_rules", O_RDONLY
);
371 if (fstat(fd
, &stat
) != 0) {
376 if (stat
.st_size
> 32767) {
377 // for safety reasons
378 // ToDo: make a bit larger later
383 char *buffer
= (char *)malloc(stat
.st_size
+ 1);
384 if (buffer
== NULL
) {
389 if (read(fd
, buffer
, stat
.st_size
) < stat
.st_size
) {
395 buffer
[stat
.st_size
] = '\0';
405 // direct "command opens file" rule
407 if (parse_ref(line
, device
, node
, &line
)) {
408 const char *fileName
= get_name(line
);
409 if (fileName
!= NULL
) {
411 const char *name
= get_name(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
;
420 strlcpy(head
->name
, fileName
, B_FILE_NAME_LENGTH
);
421 head
->confidence
= confidence
;
423 Rule
*rule
= get_rule(name
, LAUNCH_TYPE
);
434 // unknown rule - ignore line
450 Rule::Rule(rule_type type
)
457 fUnknownFileOpened(0)
467 while ((head
= (struct head
*)list_remove_head_item(&fHeads
)) != NULL
) {
472 while ((body
= (struct body
*)list_remove_head_item(&fBodies
)) != NULL
) {
479 Rule::AddHead(struct head
*head
)
481 list_add_item(&fHeads
, head
);
487 Rule::AddBody(struct body
*body
)
489 list_add_item(&fBodies
, body
);
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
)
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
514 // node doesn't exist anymore
515 head
->confidence
= -1;
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
)
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
)
550 dprintf("%ld (%ld), %Ld us\n", head
->used_count
,
551 head
->used_count
- fAppliedCount
, head
->timestamp
);
559 RuleMatcher::RuleMatcher(team_id team
, const char *name
)
563 recursive_lock_lock(&sLock
);
568 fRules
= sTeamHash
->Lookup(team
);
572 fRules
= new team_rules
;
577 list_init(&fRules
->states
);
578 list_init(&fRules
->applied
);
580 dprintf("new rules for \"%s\"\n", 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
);
597 RuleMatcher::_CollectRules(const char *name
)
599 struct rules
*rules
= sRulesHash
->Lookup(name
);
601 // there are no rules for this command
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
;
612 TRACE(("found rule %p for \"%s\"\n", rule
, rules
->name
));
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)
620 list_add_item(&fRules
->applied
, state
);
622 list_add_item(&fRules
->states
, state
);
628 RuleMatcher::GotFile(mount_id device
, vnode_id node
)
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)
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
);
651 state
->rule
->KnownFileOpened();
653 if (head
->used_count
> 1)
654 head
->timestamp
= (head
->timestamp
* (head
->used_count
- 1) + diff
) / head
->used_count
;
656 state
->rule
->UnknownFileOpened();
662 RuleMatcher::GotArguments(int32 argCount
, char * const *args
)
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)
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
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
];
697 && vfs_get_vnode_name(vnode
, buffer
, sizeof(buffer
)) == B_OK
)
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
);
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
);
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
;
729 struct rules
*rules
= sRulesHash
->Clear(true);
730 while ((rules
!= NULL
) {
731 Rule
*rule
= rules
->first
;
733 Rule
*next
= rule
->Next();
735 dprintf("Rule %p \"%s\"\n", rule
, rules
->name
);
742 struct rules
*next
= rules
->next
;
749 recursive_lock_destroy(&sLock
);
756 sTeamHash
= new(std::nothrow
) TeamTable();
757 if (sTeamHash
== NULL
|| sTeamHash
->Init(64) != B_OK
)
762 sRulesHash
= new(std::nothrow
) RuleTable();
763 if (sRulesHash
== NULL
|| sRulesHash
->Init(64) != B_OK
) {
768 recursive_lock_init(&sLock
, "rule based prefetcher");
776 std_ops(int32 op
, ...)
782 case B_MODULE_UNINIT
:
792 static struct cache_module_info sRuleBasedPrefetcherModule
= {
794 CACHE_MODULES_NAME
"/rule_based_prefetcher/v1",
804 module_info
*modules
[] = {
805 (module_info
*)&sRuleBasedPrefetcherModule
,