Sunday, August 24, 2014

Update JavaFX BarChart in background thread, to display /proc/meminfo.

Similar to previous post "Display /proc/meminfo on JavaFX PieChart", this example show how to display memory usage, retrieved from /proc/meminfo, on JavaFX BarChart.



Another main point of this post is to show the different of loading data in various stage. There are three position in the code to load barchart data.
  • At position A: call prepareMemInfo() to load barchart data in JavaFX Application Thread, before UI show.
  • At position B, call prepareMemInfo() to load barchart data in JavaFX Application Thread, after UI show.
  • At position C, start another thread to load barchart data.
    In this case, another question rised: in prepareMemInfo(), the file /proc/meminfo is parsed in sequency, but add data to series2 in Application Thread by calling Platform.runLater(). May be it will not in the same sequency. <- I'm not sure here.
Check this video to know the different:


package javafx_meminfo;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 *
 * @web http://java-buddy.blogspot.com/
 */
public class JavaFX_meminfo extends Application {

    final static String FILE_MEMINFO = "/proc/meminfo";

    private final TableView<Record> tableMem = new TableView<>();
    ObservableList<Record> listRecords = FXCollections.observableArrayList();

    XYChart.Series series1 = new XYChart.Series();  //load in Application Thread
    XYChart.Series series2 = new XYChart.Series();  //load in Background Thread
    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final BarChart<String, Number> barChart = new BarChart<>(xAxis, yAxis);

    @Override
    public void start(Stage primaryStage) {

        //position A - run in UI Thread, before UI show
        //prepareMemInfo();
        
        tableMem.setEditable(false);

        TableColumn colMemField = new TableColumn("Field");
        colMemField.setMinWidth(150);
        colMemField.setCellValueFactory(
            new PropertyValueFactory<>("memField"));

        TableColumn colMemValue = new TableColumn("Value");
        colMemValue.setMinWidth(100);
        colMemValue.setCellValueFactory(
            new PropertyValueFactory<>("memValue"));

        TableColumn colMemUnit = new TableColumn("Unit");
        colMemUnit.setMinWidth(50);
        colMemUnit.setCellValueFactory(
            new PropertyValueFactory<>("memUnit"));

        TableColumn colMemQty = new TableColumn("Qty");
        colMemQty.setMinWidth(200);
        colMemQty.setCellValueFactory(
            new PropertyValueFactory<>("memQty"));

        tableMem.setItems(listRecords);
        tableMem.getColumns().addAll(colMemField,
            colMemValue, colMemUnit, colMemQty);
        
        //Create a dummy ProgressBar running 
        ProgressTask progressTask = new ProgressTask();
        ProgressBar progressBar = new ProgressBar();
        progressBar.setProgress(0);
        progressBar.progressProperty().bind(progressTask.progressProperty());
        
        new Thread(progressTask).start();
        //---
        
        VBox vBox = new VBox();
        vBox.getChildren().addAll(tableMem, progressBar);

        StackPane root = new StackPane();
        root.getChildren().add(vBox);

        Scene scene = new Scene(root, 500, 250);

        primaryStage.setTitle("java-buddy");
        primaryStage.setScene(scene);
        primaryStage.show();

        //Open second window
        barChart.setTitle(FILE_MEMINFO);
        xAxis.setLabel("Field");
        yAxis.setLabel("Qty");
        barChart.getData().addAll(series1, series2);

        StackPane secondaryLayout = new StackPane();
        secondaryLayout.getChildren().add(barChart);

        Scene secondScene = new Scene(secondaryLayout, 500, 400);

        Stage secondStage = new Stage();
        secondStage.setTitle("Second Stage");
        secondStage.setScene(secondScene);

        //Set position of second window, related to primary window.
        secondStage.setX(primaryStage.getX() + 250);
        secondStage.setY(primaryStage.getY() + 20);
        secondStage.show();

        //position B - run in UI Thread, after UI show
        //prepareMemInfo();
        
        //position C - run in background Thread
        new Thread(BackgroundPrepareMemoInfo).start();
    }

    public static void main(String[] args) {
        launch(args);
    }

    Runnable BackgroundPrepareMemoInfo = () -> {
        prepareMemInfo();
    };

    private void prepareMemInfo() {
        
        //dummy delay
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(JavaFX_meminfo.class.getName()).log(Level.SEVERE, null, ex);
        }

        Stream<String> streamMemInfo = readFile(FILE_MEMINFO);
        streamMemInfo.forEach((line) -> {
            System.out.println(line);

            //split one line by whitespace/grouped whitespaces
            String[] oneLine = line.split("\\s+");

            for (String ele : oneLine) {
                System.out.println(ele);
            }

            System.out.println("---");

            String rField = "";
            int rMemValue = 0;
            String rMemUnit = "";

            if (oneLine.length <= 3) {
                if (oneLine.length == 3) {
                    rField = oneLine[0];
                    rMemValue = Integer.parseInt(oneLine[1]);
                    rMemUnit = oneLine[2];
                } else if (oneLine.length == 2) {
                    rField = oneLine[0];
                    rMemValue = Integer.parseInt(oneLine[1]);
                    rMemUnit = "B";
                } else if (oneLine.length == 1) {
                    rField = oneLine[0];
                    rMemValue = 0;
                    rMemUnit = "B";
                }

                Record record = new Record(
                    rField, rMemValue, rMemUnit);
                listRecords.add(record);

                if (Platform.isFxApplicationThread()) {
                    //It's running in UI Thread
                    series1.getData().add(new XYChart.Data(record.getMemField(), record.getMemQty()));
                } else {
                    //It's running in background Thread
                    Platform.runLater(() -> {
                        series2.getData().add(new XYChart.Data(record.getMemField(), record.getMemQty()));
                    });
                }

            }

        });
    }

    private Stream<String> readFile(String filePath) {
        Path path = Paths.get(filePath);

        Stream<String> fileLines = null;
        try {
            fileLines = Files.lines(path);
        } catch (IOException ex) {
            Logger.getLogger(JavaFX_meminfo.class.getName()).log(Level.SEVERE, null, ex);
        }

        return fileLines;
    }

    public static class Record {

        private final SimpleStringProperty memField;
        private final SimpleIntegerProperty memValue;
        private final SimpleStringProperty memUnit;
        private final SimpleFloatProperty memQty;

        private Record(String memField, int memValue, String memUnit) {
            this.memField = new SimpleStringProperty(memField);

            this.memValue = new SimpleIntegerProperty(memValue);
            this.memUnit = new SimpleStringProperty(memUnit);
            if (memValue == 0) {
                this.memQty = new SimpleFloatProperty(0.0f);
            } else if (memUnit.equalsIgnoreCase("MB")) {
                this.memQty = new SimpleFloatProperty((float) memValue * 1000000.0f);
            } else if (memUnit.equalsIgnoreCase("kB")) {
                this.memQty = new SimpleFloatProperty((float) memValue * 1000.0f);
            } else {
                this.memQty = new SimpleFloatProperty((float) memValue);
            }
        }

        public String getMemField() {
            return memField.get();
        }

        public int getMemValue() {
            return memValue.get();
        }

        public String getMemUnit() {
            return memUnit.get();
        }

        public float getMemQty() {
            return memQty.get();
        }

    }
    
    final int MAX_PROGRESS = 50;
    class ProgressTask extends Task<Void>{
         
        @Override
        protected Void call() throws Exception {
            for (int i = 1; i <= MAX_PROGRESS; i++) {
                updateProgress(i, MAX_PROGRESS);
                Thread.sleep(200);
            }
            return null;
        }
         
    }

}


2 comments:

  1. Hello Admin!

    Nice blog! I am editor at Java Code Geeks (www.javacodegeeks.com). We have the JCG program (see www.javacodegeeks.com/join-us/jcg/), that I think you’d be perfect for.

    If you’re interested, send me an email to eleftheria[dot]kiourtzoglou[at]javacodegeeks[dot]com and we can discuss further.

    Best regards,
    Eleftheria Kiourtzoglou

    ReplyDelete
    Replies
    1. hello Eleftheria Kiourtzoglou,

      you can re-post my blog posts, with link to the original posts.

      Delete