-
Notifications
You must be signed in to change notification settings - Fork 5
#95 Implement cache purging (admin + non-admin) with timing + logging #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
91893a1
47d2df6
ce99e1e
ebd641d
e679860
a1d9ed1
c02aca9
a4c3e32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,7 +95,21 @@ public enum Mode { CLI, GUI } | |
| // benchmarks and operations | ||
| public static HashMap<String, Benchmark> benchmarks = new HashMap<>(); | ||
| public static HashMap<String, BenchmarkOperation> operations = new HashMap<>(); | ||
| //Cli Mode | ||
| public static boolean isCliMode = false; | ||
|
|
||
| public static boolean isWindows() { | ||
| return os != null && os.toLowerCase().contains("win"); | ||
| } | ||
|
|
||
| public static boolean isLinux() { | ||
| return os != null && os.toLowerCase().contains("linux"); | ||
| } | ||
|
|
||
| public static boolean isMac() { | ||
| return os != null && os.toLowerCase().contains("mac"); | ||
| } | ||
|
|
||
| /** | ||
| * @param args the command line arguments | ||
| */ | ||
|
|
@@ -160,29 +174,59 @@ public static String getVersion() { | |
| /** | ||
| * Initialize the GUI Application. | ||
| */ | ||
| /** | ||
| * Initialize the GUI Application. | ||
| */ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is super minor but lines 173-174 lost a valid space. |
||
| public static void init() { | ||
|
|
||
|
|
||
| // ========= SAFE ELEVATION CHECK (NETBEANS + EXE/JAR) ============ | ||
| if (!UtilOs.isProcessElevated()) { | ||
|
|
||
| // Detect if running inside NetBeans (dev mode) | ||
| String classPath = System.getProperty("java.class.path"); | ||
| boolean runningInNetBeans = | ||
| classPath != null && classPath.toLowerCase().contains("netbeans"); | ||
|
|
||
| if (!runningInNetBeans) { | ||
| // Only elevate when running as packaged JAR/EXE | ||
| System.out.println("Restarting JDiskMark with Administrator privileges..."); | ||
| UtilOs.restartAsAdmin(); | ||
| return; | ||
| } else { | ||
| System.out.println("Running inside NetBeans — skipping elevation."); | ||
| } | ||
| } | ||
| // ================================================================ | ||
|
|
||
|
|
||
| os = System.getProperty("os.name"); | ||
| arch = System.getProperty("os.arch"); | ||
| processorName = Util.getProcessorName(); | ||
| jdk = Util.getJvmInfo(); | ||
|
|
||
|
|
||
| // Processor name based on OS | ||
| if (os.startsWith("Windows")) { | ||
| processorName = UtilOs.getProcessorNameWindows(); | ||
| } else if (os.startsWith("Mac OS")) { | ||
| processorName = UtilOs.getProcessorNameMacOS(); | ||
| } else if (os.contains("Linux")) { | ||
| processorName = UtilOs.getProcessorNameLinux(); | ||
| } | ||
|
|
||
| checkPermission(); | ||
| if (!APP_CACHE_DIR.exists()) { | ||
| APP_CACHE_DIR.mkdirs(); | ||
| } | ||
|
|
||
| if (mode == Mode.GUI) { | ||
| loadConfig(); | ||
| } | ||
|
|
||
| // initialize data dir if necessary | ||
| if (locationDir == null) { | ||
| locationDir = new File(System.getProperty("user.home")); | ||
| dataDir = new File(locationDir.getAbsolutePath() | ||
| + File.separator + DATADIRNAME); | ||
| } | ||
|
|
||
| if (mode == Mode.GUI) { | ||
| Gui.configureLaf(); | ||
| Gui.mainFrame = new MainFrame(); | ||
|
|
@@ -193,25 +237,19 @@ public static void init() { | |
| Gui.mainFrame.setLocationRelativeTo(null); | ||
| Gui.progressBar = Gui.mainFrame.getProgressBar(); | ||
| } | ||
|
|
||
| if (App.autoSave) { | ||
| // configure the embedded DB in .jdm | ||
| System.setProperty("derby.system.home", APP_CACHE_DIR_NAME); | ||
| loadBenchmarks(); | ||
| } | ||
|
|
||
| if (mode == Mode.GUI) { | ||
| // load current drive | ||
| Gui.updateDiskInfo(); | ||
| Gui.mainFrame.setVisible(true); | ||
| // save configuration on exit... | ||
| Runtime.getRuntime().addShutdownHook(new Thread() { | ||
| @Override | ||
| public void run() { App.saveConfig(); } | ||
| }); | ||
| Runtime.getRuntime().addShutdownHook(new Thread(App::saveConfig)); | ||
| } | ||
| } | ||
|
|
||
| public static void checkPermission() { | ||
| String osName = System.getProperty("os.name"); | ||
| if (osName.contains("Linux")) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,3 @@ | ||
|
|
||
| package jdiskmark; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||
|
|
@@ -27,6 +26,8 @@ | |
| import java.time.format.DateTimeFormatter; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import jakarta.persistence.Enumerated; | ||
| import jakarta.persistence.EnumType; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
|
|
@@ -184,6 +185,37 @@ public void serialize(UUID value, JsonGenerator gen, SerializerProvider serializ | |
| BenchmarkType benchmarkType; | ||
| public BenchmarkType getBenchmarkType() { return benchmarkType; } | ||
|
|
||
| public enum CachePurgeMethod { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vpstackhub I was thinking how about calling the top level operation alternative could use |
||
| NONE, | ||
| DROP_CACHE, | ||
| SOFT_PURGE; | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return switch (this) { | ||
| case DROP_CACHE -> "Drop Cache (OS Flush)"; | ||
| case SOFT_PURGE -> "Soft Purge (Read-Through)"; | ||
| case NONE -> "None"; | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| // --------------------------------------------------- | ||
| // Cache purge metadata (for read-after-write benchmarks) | ||
| // --------------------------------------------------- | ||
| @Column | ||
| boolean cachePurgePerformed; | ||
|
|
||
| @Column | ||
| long cachePurgeSizeBytes; | ||
|
|
||
| @Column | ||
| long cachePurgeDurationMs; | ||
|
|
||
| @Column | ||
| @Enumerated(EnumType.STRING) | ||
| CachePurgeMethod cachePurgeMethod; | ||
|
|
||
| // timestamps | ||
| @Convert(converter = LocalDateTimeAttributeConverter.class) | ||
| @Column(name = "startTime", columnDefinition = "TIMESTAMP") | ||
|
|
@@ -194,7 +226,7 @@ public void serialize(UUID value, JsonGenerator gen, SerializerProvider serializ | |
|
|
||
|
|
||
| @OneToMany(mappedBy = "benchmark", cascade = CascadeType.ALL, orphanRemoval = true) | ||
| List<BenchmarkOperation> operations; | ||
| List<BenchmarkOperation> operations = new ArrayList<>(); | ||
|
|
||
| public List<BenchmarkOperation> getOperations() { | ||
| return operations; | ||
|
|
@@ -252,16 +284,22 @@ public String toResultString() { | |
| } | ||
|
|
||
| public Benchmark() { | ||
| operations = new ArrayList<>(); | ||
| startTime = LocalDateTime.now(); | ||
| cachePurgePerformed = false; | ||
| cachePurgeSizeBytes = 0L; | ||
| cachePurgeDurationMs = 0L; | ||
| cachePurgeMethod = CachePurgeMethod.NONE; | ||
| appVersion = App.VERSION; | ||
| profileName = App.activeProfile.getName(); | ||
| } | ||
|
|
||
| Benchmark(BenchmarkType type) { | ||
| operations = new ArrayList<>(); | ||
| startTime = LocalDateTime.now(); | ||
| benchmarkType = type; | ||
| cachePurgePerformed = false; | ||
| cachePurgeSizeBytes = 0L; | ||
| cachePurgeDurationMs = 0L; | ||
| cachePurgeMethod = CachePurgeMethod.NONE; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| appVersion = App.VERSION; | ||
| profileName = App.activeProfile.getName(); | ||
| } | ||
|
|
@@ -295,7 +333,24 @@ public String getDuration() { | |
| long diffMs = Duration.between(startTime, endTime).toMillis(); | ||
| return String.valueOf(diffMs); | ||
| } | ||
|
|
||
|
|
||
| // cache purge getters (for UI / JSON) | ||
| public boolean isCachePurgePerformed() { | ||
| return cachePurgePerformed; | ||
| } | ||
|
|
||
| public long getCachePurgeSizeBytes() { | ||
| return cachePurgeSizeBytes; | ||
| } | ||
|
|
||
| public long getCachePurgeDurationMs() { | ||
| return cachePurgeDurationMs; | ||
| } | ||
|
|
||
| public CachePurgeMethod getCachePurgeMethod() { | ||
| return cachePurgeMethod; | ||
| } | ||
|
|
||
| // utility methods for collection | ||
|
|
||
| @JsonIgnore | ||
|
|
@@ -333,7 +388,7 @@ static int delete(List<UUID> benchmarkIds) { | |
| .setParameter("benchmarkIds", benchmarkIds) | ||
| .executeUpdate(); | ||
|
|
||
| // delete the parent BenchmarkOperation records | ||
| // delete the parent Benchmark records | ||
| String deleteBenchmarksJpql = "DELETE FROM Benchmark b WHERE b.id IN :benchmarkIds"; | ||
| int deletedBenchmarksCount = em.createQuery(deleteBenchmarksJpql) | ||
| .setParameter("benchmarkIds", benchmarkIds) | ||
|
|
@@ -347,4 +402,4 @@ static int delete(List<UUID> benchmarkIds) { | |
| em.getTransaction().commit(); | ||
| return deletedBenchmarksCount; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,7 @@ public static int[][] divideIntoRanges(int startIndex, int endIndex, int numThre | |
| protected Benchmark doInBackground() throws Exception { | ||
|
|
||
| if (App.verbose) { | ||
| msg(">>> USING CUSTOM PURGE BUILD (V1)"); | ||
| msg("*** starting new worker thread"); | ||
| msg("Running readTest " + App.isReadEnabled() + " writeTest " + App.isWriteEnabled()); | ||
| msg("num samples: " + App.numOfSamples + ", num blks: " + App.numOfBlocks | ||
|
|
@@ -238,13 +239,77 @@ protected Benchmark doInBackground() throws Exception { | |
| App.wIops = wOperation.iops; | ||
| Gui.mainFrame.refreshWriteMetrics(); | ||
| } | ||
|
|
||
| System.out.println(">>> BenchmarkWorker: ENTERING CACHE PURGE SECTION"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this could be used: |
||
|
|
||
| // ------------------------------------------------------------ | ||
| // Cache Purge Step (only for Read-after-Write scenario) | ||
| // ------------------------------------------------------------ | ||
| if (App.isWriteEnabled() && App.isReadEnabled() && !isCancelled()) { | ||
|
|
||
| long purgeStartNs = System.nanoTime(); | ||
| benchmark.cachePurgePerformed = true; | ||
| benchmark.cachePurgeMethod = App.isAdmin | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. potentially and |
||
| ? Benchmark.CachePurgeMethod.DROP_CACHE | ||
| : Benchmark.CachePurgeMethod.SOFT_PURGE; | ||
|
|
||
| // Detect purge size roughly equal to the full test data footprint | ||
| long estimatedBytes = (long) App.numOfSamples * (long) App.numOfBlocks * | ||
| ((long) App.blockSizeKb * 1024L); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing that we might run into is if the bytes we purge is less than the actual disk cache the benchmark would still be affected by the disk cache. When i asked gemini it recommended purging the size of the entire main memory to be safe as that was the only way to guarantee that the disk cache does not affect our benchmark even if the disk cache is usually just a portion of main memory and for this reason if we go the safe route the the process might be a little lengthy since different system could have different amounts of memory installed, might be worth notifying user what is it doing so they understand why the clearing is taking so long. maybe we can default to purging the size of system memory + 10% (need to detect it from ), i think it's simply |
||
| benchmark.cachePurgeSizeBytes = estimatedBytes; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is set from zero to the actual purge size and only if not admin and a purge operation occurs. |
||
|
|
||
| Gui.msg("⚡ Starting cache purge (" + benchmark.cachePurgeMethod + | ||
| ", ~" + (estimatedBytes / (1024*1024)) + " MB)"); | ||
|
|
||
| if (App.isAdmin) { | ||
| // ========================== | ||
| // ADMIN MODE: Use drop-cache | ||
| // ========================== | ||
|
|
||
| System.out.println(">>> BenchmarkWorker: ADMIN purge path selected"); | ||
| System.out.println(">>> BenchmarkWorker: Calling UtilOs.dropOsCache() ..."); | ||
|
|
||
| try { | ||
| boolean ok = UtilOs.dropOsCache(); | ||
|
|
||
| System.out.println(">>> BenchmarkWorker: dropOsCache() returned = " + ok); | ||
|
|
||
| if (!ok) { | ||
| System.out.println(">>> BenchmarkWorker: dropOsCache FAILED — fallback to SOFT purge"); | ||
| Gui.msg("⚠ drop-cache failed — switching to soft purge."); | ||
|
|
||
| System.out.println(">>> BenchmarkWorker: Starting SOFT purge (fallback)"); | ||
| Util.readPurge(estimatedBytes, pct -> setProgress(Math.min(99, pct))); | ||
| System.out.println(">>> BenchmarkWorker: Soft purge completed."); | ||
| } | ||
| } catch (Exception ex) { | ||
| System.out.println(">>> BenchmarkWorker: EXCEPTION in dropOsCache — fallback to read purge"); | ||
| ex.printStackTrace(); | ||
| Gui.msg("⚠ Exception during drop-cache, fallback to read purge."); | ||
| Util.readPurge(estimatedBytes, pct -> setProgress(Math.min(99, pct))); | ||
| } | ||
|
|
||
| // TODO: review renaming all files to clear catch | ||
| if (App.isReadEnabled() && App.isWriteEnabled() && !isCancelled()) { | ||
| // TODO: review refactor to App.dropCache() & Gui.dropCache() | ||
| Gui.dropCache(); | ||
| } else { | ||
| // ========================== | ||
| // USER MODE: Soft purge | ||
| // ========================== | ||
| System.out.println(">>> BenchmarkWorker: Starting SOFT purge (readPurge)"); | ||
| Gui.msg("Performing soft cache purge (read-through)…"); | ||
|
|
||
| Util.readPurge(estimatedBytes, pct -> setProgress(Math.min(99, pct))); | ||
|
|
||
| System.out.println(">>> BenchmarkWorker: Soft purge completed."); | ||
| } | ||
|
|
||
| long purgeEndNs = System.nanoTime(); | ||
| long purgeDurationMs = (purgeEndNs - purgeStartNs) / 1_000_000L; | ||
|
|
||
| benchmark.cachePurgeDurationMs = purgeDurationMs; | ||
|
|
||
| Gui.msg("✔ Purge finished in " + purgeDurationMs + " ms"); | ||
| } | ||
|
|
||
|
|
||
| if (App.isReadEnabled()) { | ||
| BenchmarkOperation rOperation = new BenchmarkOperation(); | ||
| rOperation.setBenchmark(benchmark); | ||
|
|
@@ -369,7 +434,11 @@ protected void process(List<Sample> sampleList) { | |
| case Sample.Type.WRITE -> Gui.addWriteSample(s); | ||
| case Sample.Type.READ -> Gui.addReadSample(s); | ||
| } | ||
| }); | ||
| }); | ||
| if (App.isCliMode) { | ||
| int pct = this.getProgress(); | ||
| printCliProgress(pct); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -380,4 +449,17 @@ protected void done() { | |
| App.state = App.State.IDLE_STATE; | ||
| Gui.mainFrame.adjustSensitivity(); | ||
| } | ||
|
|
||
| private void printCliProgress(int pct) { | ||
| int bars = pct / 5; // 20-bar progress | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. super minor, there is a java practice where the recommendation is to avoid abbreviations when possible so |
||
| int spaces = 20 - bars; | ||
|
|
||
| String bar = "[" + "#".repeat(bars) + "-".repeat(spaces) + "] " + pct + "%"; | ||
|
|
||
| System.out.print("\r" + bar); | ||
|
|
||
| if (pct >= 100) { | ||
| System.out.println(); | ||
| } | ||
| } | ||
| } | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vpstackhub what version of netbeans are you using, interesting that this got checked in which seems to break running in netbeans for others?
specifically this line because it does not exist for other users.
project.dir=C:/Users/valer/OneDrive/Documenti/NetBeansProjects/jdm-javaMight you know why these attributes are being added into the project file?