blob: 1f10c0fdb0bc2350726061987d6ddf55a98de23d [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02002// SPDX-License-Identifier: Apache-2.0
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02003
4package logtree
5
Tim Windelschmidt9f21f532024-05-07 15:14:20 +02006import "source.monogon.dev/osbase/logbuffer"
Serge Bazanskif68153c2020-10-26 13:54:37 +01007
Serge Bazanski216fe7b2021-05-21 18:36:16 +02008// entry is a journal entry, representing a single log event (encompassed in a
9// Payload) at a given DN. See the journal struct for more information about the
10// global/local linked lists.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020011type entry struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020012 // origin is the DN at which the log entry was recorded, or conversely, in which DN
13 // it will be available at.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020014 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020015 // journal is the parent journal of this entry. An entry can belong only to a
16 // single journal. This pointer is used to mutate the journal's head/tail pointers
17 // when unlinking an entry.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020018 journal *journal
Serge Bazanski216fe7b2021-05-21 18:36:16 +020019 // leveled is the leveled log entry for this entry, if this log entry was emitted
20 // by leveled logging. Otherwise it is nil.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020021 leveled *LeveledPayload
Serge Bazanski216fe7b2021-05-21 18:36:16 +020022 // raw is the raw log entry for this entry, if this log entry was emitted by raw
23 // logging. Otherwise it is nil.
Serge Bazanskif68153c2020-10-26 13:54:37 +010024 raw *logbuffer.Line
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020025
Serge Bazanski216fe7b2021-05-21 18:36:16 +020026 // prevGlobal is the previous entry in the global linked list, or nil if this entry
27 // is the oldest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020028 prevGlobal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020029 // nextGlobal is the next entry in the global linked list, or nil if this entry is
30 // the newest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020031 nextGlobal *entry
32
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033 // prevLocal is the previous entry in this entry DN's local linked list, or nil if
34 // this entry is the oldest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020035 prevLocal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020036 // prevLocal is the next entry in this entry DN's local linked list, or nil if this
37 // entry is the newest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020038 nextLocal *entry
39
Serge Bazanski216fe7b2021-05-21 18:36:16 +020040 // seqLocal is a counter within a local linked list that increases by one each time
41 // a new log entry is added. It is used to quickly establish local linked list
42 // sizes (by subtracting seqLocal from both ends). This setup allows for O(1)
43 // length calculation for local linked lists as long as entries are only unlinked
44 // from the head or tail (which is the case in the current implementation).
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020045 seqLocal uint64
46}
47
Serge Bazanski216fe7b2021-05-21 18:36:16 +020048// external returns a LogEntry object for this entry, ie. the public version of
49// this object, without fields relating to the parent journal, linked lists,
50// sequences, etc. These objects are visible to library consumers.
Serge Bazanskif68153c2020-10-26 13:54:37 +010051func (e *entry) external() *LogEntry {
52 return &LogEntry{
53 DN: e.origin,
54 Leveled: e.leveled,
55 Raw: e.raw,
56 }
57}
58
Serge Bazanski216fe7b2021-05-21 18:36:16 +020059// unlink removes this entry from both global and local linked lists, updating the
60// journal's head/tail pointers if needed. journal.mu must be taken as RW
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020061func (e *entry) unlink() {
62 // Unlink from the global linked list.
63 if e.prevGlobal != nil {
64 e.prevGlobal.nextGlobal = e.nextGlobal
65 }
66 if e.nextGlobal != nil {
67 e.nextGlobal.prevGlobal = e.prevGlobal
68 }
69 // Update journal head/tail pointers.
70 if e.journal.head == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020071 e.journal.head = e.nextGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020072 }
73 if e.journal.tail == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020074 e.journal.tail = e.prevGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020075 }
76
77 // Unlink from the local linked list.
78 if e.prevLocal != nil {
79 e.prevLocal.nextLocal = e.nextLocal
80 }
81 if e.nextLocal != nil {
82 e.nextLocal.prevLocal = e.prevLocal
83 }
84 // Update journal head/tail pointers.
85 if e.journal.heads[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020086 e.journal.heads[e.origin] = e.nextLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020087 }
88 if e.journal.tails[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020089 e.journal.tails[e.origin] = e.prevLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020090 }
91}
92
93// quota describes the quota policy for logging at a given DN.
94type quota struct {
95 // origin is the exact DN that this quota applies to.
96 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020097 // max is the maximum count of log entries permitted for this DN - ie, the maximum
98 // size of the local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020099 max uint64
100}
101
102// append adds an entry at the head of the global and local linked lists.
103func (j *journal) append(e *entry) {
104 j.mu.Lock()
105 defer j.mu.Unlock()
106
107 e.journal = j
108
109 // Insert at head in global linked list, set pointers.
110 e.nextGlobal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200111 e.prevGlobal = j.tail
112 if j.tail != nil {
113 j.tail.nextGlobal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200114 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200115 j.tail = e
116 if j.head == nil {
117 j.head = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200118 }
119
120 // Create quota if necessary.
121 if _, ok := j.quota[e.origin]; !ok {
122 j.quota[e.origin] = &quota{origin: e.origin, max: 8192}
123 }
124
125 // Insert at head in local linked list, calculate seqLocal, set pointers.
126 e.nextLocal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200127 e.prevLocal = j.tails[e.origin]
128 if j.tails[e.origin] != nil {
129 j.tails[e.origin].nextLocal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200130 e.seqLocal = e.prevLocal.seqLocal + 1
131 } else {
132 e.seqLocal = 0
133 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200134 j.tails[e.origin] = e
135 if j.heads[e.origin] == nil {
136 j.heads[e.origin] = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200137 }
138
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200139 // Apply quota to the local linked list that this entry got inserted to, ie. remove
140 // elements in excess of the quota.max count.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200141 quota := j.quota[e.origin]
Serge Bazanski8fab0142023-03-29 16:48:16 +0200142 count := (j.tails[e.origin].seqLocal - j.heads[e.origin].seqLocal) + 1
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200143 if count > quota.max {
Serge Bazanski8fab0142023-03-29 16:48:16 +0200144 // Keep popping elements off the head of the local linked list until quota is not
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200145 // violated.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200146 left := count - quota.max
Serge Bazanski8fab0142023-03-29 16:48:16 +0200147 cur := j.heads[e.origin]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200148 for {
149 // This shouldn't happen if quota.max >= 1.
150 if cur == nil {
151 break
152 }
153 if left == 0 {
154 break
155 }
156 el := cur
157 cur = el.nextLocal
158 // Unlinking the entry unlinks it from both the global and local linked lists.
159 el.unlink()
160 left -= 1
161 }
162 }
163}