Commit | Line | Data |
---|---|---|
a52fde77 | 1 | /******************************************************************************* |
60ae41e1 | 2 | * Copyright (c) 2012, 2014 Ericsson |
a52fde77 AM |
3 | * Copyright (c) 2010, 2011 École Polytechnique de Montréal |
4 | * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com> | |
3de60236 | 5 | * |
a52fde77 AM |
6 | * All rights reserved. This program and the accompanying materials are |
7 | * made available under the terms of the Eclipse Public License v1.0 which | |
8 | * accompanies this distribution, and is available at | |
9 | * http://www.eclipse.org/legal/epl-v10.html | |
3de60236 | 10 | * |
a52fde77 AM |
11 | *******************************************************************************/ |
12 | ||
e894a508 | 13 | package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; |
a52fde77 | 14 | |
1f48ef66 AM |
15 | import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; |
16 | ||
a52fde77 AM |
17 | import java.io.File; |
18 | import java.io.FileInputStream; | |
19 | import java.io.FileOutputStream; | |
20 | import java.io.IOException; | |
3b7f5abe | 21 | import java.nio.channels.ClosedChannelException; |
a52fde77 | 22 | import java.nio.channels.FileChannel; |
b2648641 | 23 | import java.util.logging.Logger; |
a52fde77 | 24 | |
aa353506 | 25 | import org.eclipse.jdt.annotation.NonNull; |
1f48ef66 AM |
26 | import java.util.Objects; |
27 | import java.util.concurrent.ExecutionException; | |
28 | ||
29 | import org.eclipse.jdt.annotation.Nullable; | |
f4baf640 | 30 | import org.eclipse.tracecompass.common.core.log.TraceCompassLog; |
49a817d5 | 31 | import org.eclipse.tracecompass.internal.statesystem.core.Activator; |
f4baf640 | 32 | import org.eclipse.tracecompass.internal.statesystem.core.backend.historytree.IHistoryTree.IHTNodeFactory; |
49a817d5 | 33 | |
1f48ef66 AM |
34 | import com.google.common.cache.CacheBuilder; |
35 | import com.google.common.cache.CacheLoader; | |
36 | import com.google.common.cache.LoadingCache; | |
37 | ||
a52fde77 | 38 | /** |
360f899e EB |
39 | * This class abstracts inputs/outputs of the HistoryTree nodes. |
40 | * | |
41 | * It contains all the methods and descriptors to handle reading/writing nodes | |
42 | * to the tree-file on disk and all the caching mechanisms. | |
43 | * | |
49a817d5 MK |
44 | * This abstraction is mainly for code isolation/clarification purposes. Every |
45 | * HistoryTree must contain 1 and only 1 HT_IO element. | |
3de60236 | 46 | * |
ffd0aa67 | 47 | * @author Alexandre Montplaisir |
a52fde77 | 48 | */ |
0e4eaca8 | 49 | public class HT_IO { |
4018d70a | 50 | |
1f48ef66 AM |
51 | private static final Logger LOGGER = TraceCompassLog.getLogger(HT_IO.class); |
52 | ||
53 | // ------------------------------------------------------------------------ | |
54 | // Global cache of nodes | |
55 | // ------------------------------------------------------------------------ | |
56 | ||
57 | private static final class CacheKey { | |
58 | ||
59 | public final HT_IO fStateHistory; | |
60 | public final int fSeqNumber; | |
4018d70a | 61 | |
1f48ef66 AM |
62 | public CacheKey(HT_IO stateHistory, int seqNumber) { |
63 | fStateHistory = stateHistory; | |
64 | fSeqNumber = seqNumber; | |
4018d70a MK |
65 | } |
66 | ||
1f48ef66 AM |
67 | @Override |
68 | public int hashCode() { | |
69 | return Objects.hash(fStateHistory, fSeqNumber); | |
4018d70a MK |
70 | } |
71 | ||
1f48ef66 AM |
72 | @Override |
73 | public boolean equals(@Nullable Object obj) { | |
74 | if (this == obj) { | |
75 | return true; | |
76 | } | |
77 | if (obj == null) { | |
78 | return false; | |
79 | } | |
80 | if (getClass() != obj.getClass()) { | |
81 | return false; | |
82 | } | |
83 | CacheKey other = (CacheKey) obj; | |
84 | return (fStateHistory.equals(other.fStateHistory) && | |
85 | fSeqNumber == other.fSeqNumber); | |
4018d70a MK |
86 | } |
87 | } | |
88 | ||
1f48ef66 AM |
89 | private static final int CACHE_SIZE = 256; |
90 | ||
91 | private static final LoadingCache<CacheKey, HTNode> NODE_CACHE = | |
92 | checkNotNull(CacheBuilder.newBuilder() | |
93 | .maximumSize(CACHE_SIZE) | |
94 | .build(new CacheLoader<CacheKey, HTNode>() { | |
95 | @Override | |
96 | public HTNode load(CacheKey key) throws IOException { | |
97 | HT_IO io = key.fStateHistory; | |
98 | int seqNb = key.fSeqNumber; | |
99 | ||
100 | LOGGER.finest(() -> "[HtIo:CacheMiss] seqNum=" + seqNb); //$NON-NLS-1$ | |
101 | ||
4790127e AM |
102 | synchronized (io) { |
103 | io.seekFCToNodePos(io.fFileChannelIn, seqNb); | |
f4baf640 | 104 | return HTNode.readNode(io.fConfig, io.fFileChannelIn, key.fStateHistory.fNodeFactory); |
4790127e | 105 | } |
1f48ef66 AM |
106 | } |
107 | })); | |
108 | ||
109 | ||
110 | // ------------------------------------------------------------------------ | |
111 | // Instance fields | |
112 | // ------------------------------------------------------------------------ | |
113 | ||
360f899e EB |
114 | /* Configuration of the History Tree */ |
115 | private final HTConfig fConfig; | |
a52fde77 AM |
116 | |
117 | /* Fields related to the file I/O */ | |
c772ff74 MK |
118 | private final FileInputStream fFileInputStream; |
119 | private final FileOutputStream fFileOutputStream; | |
120 | private final FileChannel fFileChannelIn; | |
121 | private final FileChannel fFileChannelOut; | |
a52fde77 | 122 | |
f4baf640 GB |
123 | private final IHTNodeFactory fNodeFactory; |
124 | ||
1f48ef66 AM |
125 | // ------------------------------------------------------------------------ |
126 | // Methods | |
127 | // ------------------------------------------------------------------------ | |
128 | ||
6993c1ca | 129 | |
b2648641 | 130 | |
a52fde77 AM |
131 | /** |
132 | * Standard constructor | |
3de60236 | 133 | * |
360f899e | 134 | * @param config |
49a817d5 | 135 | * The configuration object for the StateHistoryTree |
ab604305 | 136 | * @param newFile |
360f899e | 137 | * Flag indicating that the file must be created from scratch |
f4baf640 GB |
138 | * @param nodeFactory |
139 | * The factory to create new nodes for this tree | |
49a817d5 | 140 | * |
a52fde77 | 141 | * @throws IOException |
360f899e | 142 | * An exception can be thrown when file cannot be accessed |
a52fde77 | 143 | */ |
f4baf640 | 144 | public HT_IO(HTConfig config, boolean newFile, IHTNodeFactory nodeFactory) throws IOException { |
360f899e | 145 | fConfig = config; |
a52fde77 | 146 | |
360f899e | 147 | File historyTreeFile = config.getStateFile(); |
a52fde77 | 148 | if (newFile) { |
360f899e | 149 | boolean success1 = true; |
a52fde77 AM |
150 | /* Create a new empty History Tree file */ |
151 | if (historyTreeFile.exists()) { | |
ab604305 AM |
152 | success1 = historyTreeFile.delete(); |
153 | } | |
cb42195c | 154 | boolean success2 = historyTreeFile.createNewFile(); |
ab604305 AM |
155 | if (!(success1 && success2)) { |
156 | /* It seems we do not have permission to create the new file */ | |
157 | throw new IOException("Cannot create new file at " + //$NON-NLS-1$ | |
158 | historyTreeFile.getName()); | |
a52fde77 | 159 | } |
c772ff74 MK |
160 | fFileInputStream = new FileInputStream(historyTreeFile); |
161 | fFileOutputStream = new FileOutputStream(historyTreeFile, false); | |
a52fde77 AM |
162 | } else { |
163 | /* | |
164 | * We want to open an existing file, make sure we don't squash the | |
165 | * existing content when opening the fos! | |
166 | */ | |
c772ff74 MK |
167 | fFileInputStream = new FileInputStream(historyTreeFile); |
168 | fFileOutputStream = new FileOutputStream(historyTreeFile, true); | |
a52fde77 | 169 | } |
c772ff74 MK |
170 | fFileChannelIn = fFileInputStream.getChannel(); |
171 | fFileChannelOut = fFileOutputStream.getChannel(); | |
f4baf640 | 172 | fNodeFactory = nodeFactory; |
a52fde77 AM |
173 | } |
174 | ||
a52fde77 | 175 | /** |
8d47cc34 | 176 | * Read a node from the file on disk. |
3b7f5abe | 177 | * |
8d47cc34 AM |
178 | * @param seqNumber |
179 | * The sequence number of the node to read. | |
180 | * @return The object representing the node | |
3b7f5abe AM |
181 | * @throws ClosedChannelException |
182 | * Usually happens because the file was closed while we were | |
183 | * reading. Instead of using a big reader-writer lock, we'll | |
184 | * just catch this exception. | |
a52fde77 | 185 | */ |
4790127e | 186 | public @NonNull HTNode readNode(int seqNumber) throws ClosedChannelException { |
1f48ef66 AM |
187 | /* Do a cache lookup. If it's not present it will be loaded from disk */ |
188 | LOGGER.finest(() -> "[HtIo:CacheLookup] seqNum=" + seqNumber); //$NON-NLS-1$ | |
189 | CacheKey key = new CacheKey(this, seqNumber); | |
a52fde77 | 190 | try { |
1f48ef66 | 191 | return checkNotNull(NODE_CACHE.get(key)); |
6993c1ca | 192 | |
1f48ef66 AM |
193 | } catch (ExecutionException e) { |
194 | /* Get the inner exception that was generated */ | |
195 | Throwable cause = e.getCause(); | |
196 | if (cause instanceof ClosedChannelException) { | |
197 | throw (ClosedChannelException) cause; | |
198 | } | |
4018d70a | 199 | /* |
1f48ef66 | 200 | * Other types of IOExceptions shouldn't happen at this point though. |
4018d70a | 201 | */ |
49a817d5 | 202 | Activator.getDefault().logError(e.getMessage(), e); |
aa353506 | 203 | throw new IllegalStateException(); |
a52fde77 AM |
204 | } |
205 | } | |
206 | ||
0e4eaca8 AM |
207 | /** |
208 | * Write the given node to disk. | |
209 | * | |
210 | * @param node | |
211 | * The node to write. | |
212 | */ | |
4790127e | 213 | public void writeNode(HTNode node) { |
a52fde77 | 214 | try { |
6993c1ca | 215 | int seqNumber = node.getSequenceNumber(); |
1f48ef66 AM |
216 | |
217 | /* "Write-back" the node into the cache */ | |
218 | CacheKey key = new CacheKey(this, seqNumber); | |
219 | NODE_CACHE.put(key, node); | |
6993c1ca | 220 | |
a52fde77 | 221 | /* Position ourselves at the start of the node and write it */ |
4790127e AM |
222 | synchronized (this) { |
223 | seekFCToNodePos(fFileChannelOut, seqNumber); | |
224 | node.writeSelf(fFileChannelOut); | |
225 | } | |
a52fde77 AM |
226 | } catch (IOException e) { |
227 | /* If we were able to open the file, we should be fine now... */ | |
49a817d5 | 228 | Activator.getDefault().logError(e.getMessage(), e); |
a52fde77 AM |
229 | } |
230 | } | |
231 | ||
0e4eaca8 AM |
232 | /** |
233 | * Get the output file channel, used for writing. | |
234 | * | |
235 | * @return The output file channel | |
236 | */ | |
e8b7cc14 | 237 | public FileChannel getFcOut() { |
c772ff74 | 238 | return fFileChannelOut; |
a52fde77 AM |
239 | } |
240 | ||
0e4eaca8 AM |
241 | /** |
242 | * Retrieve the input stream with which to write the attribute tree. | |
243 | * | |
244 | * @param nodeOffset | |
245 | * The offset in the file, in number of nodes. This should be | |
246 | * after all the nodes. | |
247 | * @return The correctly-seeked input stream | |
248 | */ | |
8d47cc34 | 249 | public FileInputStream supplyATReader(int nodeOffset) { |
a52fde77 AM |
250 | try { |
251 | /* | |
252 | * Position ourselves at the start of the Mapping section in the | |
253 | * file (which is right after the Blocks) | |
254 | */ | |
c772ff74 | 255 | seekFCToNodePos(fFileChannelIn, nodeOffset); |
a52fde77 | 256 | } catch (IOException e) { |
49a817d5 | 257 | Activator.getDefault().logError(e.getMessage(), e); |
a52fde77 | 258 | } |
c772ff74 | 259 | return fFileInputStream; |
a52fde77 AM |
260 | } |
261 | ||
0e4eaca8 AM |
262 | /** |
263 | * Close all file channels and streams. | |
264 | */ | |
8d47cc34 | 265 | public synchronized void closeFile() { |
3de60236 | 266 | try { |
c772ff74 MK |
267 | fFileInputStream.close(); |
268 | fFileOutputStream.close(); | |
3de60236 | 269 | } catch (IOException e) { |
49a817d5 | 270 | Activator.getDefault().logError(e.getMessage(), e); |
3de60236 | 271 | } |
1a4205d9 AM |
272 | } |
273 | ||
0e4eaca8 AM |
274 | /** |
275 | * Delete the history tree file | |
276 | */ | |
8d47cc34 | 277 | public synchronized void deleteFile() { |
1a4205d9 | 278 | closeFile(); |
3de60236 | 279 | |
360f899e | 280 | File historyTreeFile = fConfig.getStateFile(); |
6993c1ca | 281 | if (!historyTreeFile.delete()) { |
36bf82a2 | 282 | /* We didn't succeed in deleting the file */ |
49a817d5 | 283 | Activator.getDefault().logError("Failed to delete" + historyTreeFile.getName()); //$NON-NLS-1$ |
36bf82a2 AM |
284 | } |
285 | } | |
286 | ||
a52fde77 AM |
287 | /** |
288 | * Seek the given FileChannel to the position corresponding to the node that | |
289 | * has seqNumber | |
3de60236 | 290 | * |
49a817d5 MK |
291 | * @param fc |
292 | * the channel to seek | |
293 | * @param seqNumber | |
294 | * the node sequence number to seek the channel to | |
a52fde77 | 295 | * @throws IOException |
49a817d5 | 296 | * If some other I/O error occurs |
a52fde77 AM |
297 | */ |
298 | private void seekFCToNodePos(FileChannel fc, int seqNumber) | |
299 | throws IOException { | |
a52fde77 | 300 | /* |
360f899e | 301 | * Cast to (long) is needed to make sure the result is a long too and |
a52fde77 AM |
302 | * doesn't get truncated |
303 | */ | |
3a081e85 | 304 | fc.position(IHistoryTree.TREE_HEADER_SIZE |
49a817d5 | 305 | + ((long) seqNumber) * fConfig.getBlockSize()); |
a52fde77 AM |
306 | } |
307 | ||
308 | } |