• 検索結果がありません。

JAIST Repository: リアルタイム・タスクスケジューリング・シミュレーション結果を可視化するGUI 分析ツールの開発 [課題研究報告書]

N/A
N/A
Protected

Academic year: 2021

シェア "JAIST Repository: リアルタイム・タスクスケジューリング・シミュレーション結果を可視化するGUI 分析ツールの開発 [課題研究報告書]"

Copied!
54
0
0

読み込み中.... (全文を見る)

全文

(1)

JAIST Repository

https://dspace.jaist.ac.jp/

Title

リアルタイム・タスクスケジューリング・シミュレー

ション結果を可視化するGUI 分析ツールの開発 [課題

研究報告書]

Author(s)

鈴木, 和佳子

Citation

Issue Date

2017-03

Type

Thesis or Dissertation

Text version

author

URL

http://hdl.handle.net/10119/14182

Rights

(2)

課題研究報告書

リアルタイム・タスクスケジューリング・

シミュレーション結果を可視化する

GUI

分析ツールの開発

北陸先端科学技術大学院大学

情報科学研究科

鈴木 和佳子

平成 29 年 3 月

(3)

課題研究報告書

リアルタイム・タスクスケジューリング・

シミュレーション結果を可視化する

GUI

分析ツールの開発

1310706

鈴木 和佳子

主指導教員

田中 清史

審査委員主査

田中 清史

審査委員

金子 峰雄

審査委員

井口 寧

北陸先端科学技術大学院大学

情報科学研究科

提出年月: 平成 29 年 2 月

(4)

概 要

今日、様々なリアルタイム・システムが稼動しており、リアルタイム・システムで実行さ

れているタスクの中には、極めて重要性が高く応答遅延が人命にかかわるような致命的

な影響を及ぼすリアルタイムタスクも存在する。そのため、全てのタスクでデッドライン

ミスなく指定された期限内に実施可能であるかを正確に計算し、スケジュール可能性を保

証することはますます重要になってきている。しかし、タスクスケジューリングのシミュ

レーション結果はタスクの実行ログなどによって提供されることが多く、このようなテキ

ストベースの情報から実際にスケジューリング可能性を確認することは困難である。特に

タスクの実行回数が多く対象となる時間が長い場合には、スケジューリング可能性の確認

は非常に多くの時間を要する作業となる。しかしながら、このようなタスク・スケジュー

リングのシミュレーション結果を実行ログなどの情報から図やグラフとして可視化する

ツールは現在のところあまり提供されていないのが現実である。

本研究ではリアルタイム・タスクスケジューリングのシミュレーション結果をグラフで

可視化する GUI ツールを作成した。また、グラフの作成だけでなく、ズームイン/ズーム

アウト、デッドラインミスの検索機能やツールで作成したグラフを画像ファイルとしてエ

クスポートする機能も提供している。これによってタスクの実行回数が多く対象となる時

間が長くても、スケジュール可能性を即座に確認することを可能にしているほか、論文な

どでシミュレーション結果をグラフとして図示することも容易にしている。また、本研究

では Java SE に同梱して提供されている JavaFX を用いてツールを開発しているため、極

めて簡単な準備で Windows や Linux, Mac OS などマルチプラットフォームでツールを実

行することが可能になっている。

本稿では開発したリアルタイム・タスクスケジューリングのシミュレーション結果を可

視化する GUI ツールの機能を説明し、開発に使用した JavaFX の機能や実際のコードに

ついても説明する。

(5)

目 次

第 1 章 はじめに

1

1.1

研究の背景

. . . .

1

1.2

研究の目的

. . . .

1

1.3

論文の構成

. . . .

2

第 2 章 GUI ツールの概要

3

第 3 章 GUI ツールの開発

6

3.1

開発方法 . . . .

6

3.2

入力ファイルの読み取り . . . .

6

3.3

グラフの描画 . . . .

11

3.4

チャートを部分表示する機能の実装 . . . .

17

3.5

スクロール・バーの表示とズームイン/ズームアウト機能の実装 . . . .

24

3.6

チャートを画像ファイルとしてエクスポートする機能の実装 . . . .

26

3.7

デッドラインミスを検索する機能の実装 . . . .

29

3.8

開発時に発生した問題点と解決法 . . . .

35

3.9

ツールのファイル構成およびソフトウェア規模 . . . .

38

第 4 章 実際のシミュレーション結果における適用例

41

第 5 章 まとめ

47

(6)

1

章 はじめに

1.1

研究の背景

今日、様々な組込みシステムにおいて日常的にリアルタイム・タスクが実行されてい

る。組み込みシステムでのリアルタイム性への要求は増大し、その中には車のブレーキ制

御装置・エアバッグ制御装置などデッドラインミスが致命的になりうるシステム(ハード

リアルタイムシステム)も存在する。リアルタイムシステムにおいてそのシステムが実行

するタスクが重要であればあるほど、タスクセットが真にスケジュール可能であることを

保証することが重要になる。また、複数のリアルタイム・タスクが並行して実行されてい

る時に、それらのタスクが真にスケジューリング可能であるかを検証することがますます

重要になってきている。

システム開発においてシミュレーションあるいは実行ログの精査によってスケジュール

可能性を検証することが有効であるが、テキストベースのシミュレーション結果/実行ロ

グからスケジュール可能性や応答時間の変動を判断することは困難であり、時間のかかる

作業となる。この作業をより円滑に実行するため、タスク・スケジューリングの結果を可

視化するツールが有用であるが、現在のところ広く使用可能な形態で提供されていないの

が実情である。

1.2

研究の目的

リアルタイム性を確保するためには、デッドラインミスを防ぎ応答時間を短縮するた

めのリアルタイム・タスクスケジューリング方式が重要である。リアルタイム・タスクス

ケジューリング方式に対する評価指標として代表的なものは「デッドラインミス発生の

有無/頻度」と「応答時間」である [1]。高いリアルタイム性を達成するためには既存スケ

ジューリング方式や新しいスケジューリング方式によるタスク実行の様子を観測し、デッ

ドラインミスの発生状況や応答時間の変化を確認することが重要であるが、実行ログや

シミュレーション出力は通常テキストベースであり、タスク実行状況を確認することが困

難である。また、リアルタイムスケジューリングの研究において、新規に考案したスケ

ジューリングアルゴリズムが有効に働くケースを的確に図示することが困難である場合が

ある。長い実行(シミュレーション)の中でアルゴリズムの効果が現れる個所を発見して

論文のための例図とすることが有効であるが、このためには実行結果のスケジューリング

図の取得が必要である。さらに、タスクの実行結果を可視化することによって、描画され

(7)

たタスクの実行結果の矛盾や問題点から、シミュレータのバグやシミュレータ上に実装さ

れたスケジューリングアルゴリズムのバグを発見することも可能になる。

本研究では、実行ログやリアルタイム・タスクスケジューリング・シミュレーションの

結果を可視化するツールを作成し、結果の分析を簡易化してリアルタイムシステム開発

をサポートすることを目的とする。マルチプラットフォームでの利用を考慮し、Java 言

語によるツール開発を行う。作成するツールはシミュレーション結果をグラフで図示する

GUI

ツールであり、以下のタスクスケジューリングの評価指標を可視化する。

1.

応答時間 : タスクの起動要求から実行終了までの時間が応答時間となる。

2.

デッドラインミスの有無/発生頻度 : 実行がデッドラインまでに終了しない場合、

デッドラインミスとなる。

3.

スループット : 各タスク、および全タスクによるの CPU 使用率をスループットと

する。

1.3

論文の構成

本課題研究報告では以下の構成をとる。2 章では本研究で開発した GUI ツールの概要

について述べる。3 章では GUI ツールの開発方法や機能について、実際のコードや使用し

た JavaFX の機能も交えながら説明する。4 章では実際のタスク・シミュレーション結果

を本研究の GUI ツールで分析した結果について記述する。最後に 5 章でまとめとして本

研究を総括する。

(8)

2

GUI

ツールの概要

本研究では CSV 形式のファイルを入力とする。本ツールで使用する CSV ファイルはタ

スク・スケジューリングのシミュレーション・ツールが出力することを前提とし、本研究

で開発したツールでの CSV 形式のフォーマットに合わせて出力すれば、シミュレーショ

ン結果を容易に可視化して分析することができる (図 2.1)。各行には以下の項目をカンマ

区切りで記述する。

図 2.1: GUI ツールの入力と出力

1.

各実行インスタンスのイベント時刻

2.

タスク番号

3.

ジョブ番号

4.

イベント内容(起動要求、実行開始、実行終了、デッドライン、デッドラインミス)

出力するグラフは以下を表示する。

1.

タスクの起動要求 : 上矢印

2.

タスクのデッドライン : 下矢印

(9)

3.

タスク実行の CPU 使用時間 : 四角

4.

デッドラインミスの発生箇所 : ×印

図 2.2: 作成した GUI ツールによる描画

サンプルの実行結果は図 2.2 のとおりである。

また、機能として以下を開発した。

1.

描画した結果を画像ファイルとして保存する。

2.

スクロールして表示する。

3.

デッドラインミスの発生箇所を検索し、発生箇所までジャンプして表示する。

4.

ズームイン/ズームアウトして表示する。

また、JavaFX 8 はマルチプラットフォームをサポートしており、Mac OS や Linux も

サポート対象である [2]。本 GUI ツールも図 2.3 や図 2.4 のように Mac OS 10 や Cent OS

7.2

で稼動する。

(10)

図 2.3: Mac OS 10 上で本 GUI ツールを稼動させた画面ショット

(11)

3

GUI

ツールの開発

3.1

開発方法

開発には JavaFX[3] を使用する。JavaFX はグラフィック、UI 機能を実現する Java の

リッチ・クライアント・プラットフォームであり、シーン・グラフ [4] によって描画する

個々の要素を階層ツリーで管理しており、これによって複雑な描画やアニメーションの開

発を容易にしている。グラフ描画や図形描画、グラフィカル・ユーザー・インターフェー

スの機能を開発するための API を提供しているフレームワークや言語は Qt や HTML5,

C# WPF

など複数あり、ズームやスクロールの機能は実装することができるものもある。

今回は通常の棒グラフではなく、ガントチャートのように途中で中断されるタスクの実行

を描画する必要があり、横棒以外にもタスクの起動要求を示す上矢印、デッドラインを示

す下矢印、デッドラインミスを示す×印を加えて描画する必要があった。調査を行ったと

ころ、JavaFX ではチャートのデータに X 座標や Y 座標だけでなく、extraValue として汎

用情報を付加することが可能 [5] であり、この API を利用することで単純な棒グラフでは

なく、ガントチャートの描画や上矢印や下矢印、×印を表示するといったカスタマイズを

行うことができたため、JavaFX を採用した。JavaFX はまた稼動環境の構築が容易であ

る点、マルチプラットフォームをサポート可能であるという点でも優位性がある。Java 7

Update 5

以前では JDK や JRE を導入するだけでなく、外部ライブラリである jfxrt.jar に

クラスパスを通す必要があったが、Java 8 では JavaFX は JRE に同梱されており、JDK

や JRE を導入するだけで JavaFX アプリケーションを稼動させる環境を構築することが

可能になった。したがって、使用するバージョンは JDK1.8 に同梱されている JavaFX 8

とした。開発環境には NetBeans 8.1 を使用した。

3.2

入力ファイルの読み取り

入力ファイルは CSV 形式で項目は以下である。

1.

各実行インスタンスのイベント時刻

2.

タスク番号

3.

ジョブ番号

(12)

4.

イベント内容(タスクの起動要求, 実行開始, 実行終了, デッドライン, デッドライ

ンミス)

イベントの内容は以下で記述する。

1.

タスクの起動要求 : RL

2.

実行開始 : ST

3.

実行終了 : ED

4.

デッドライン : DL

5.

デッドラインミス : DM

Listing3.1

が CSV ファイルのサンプルである。

Listing 3.1: CSV

ファイルのサンプル

0 ,2 ,1 , ST 1 ,2 ,1 , ED 1 ,1 ,1 , ST 1 ,4 ,1 , RL 3 ,1 ,1 , ED 3 ,2 ,1 , DL 3 ,2 ,2 , RL 3 ,2 ,2 , ST ... 7 ,4 ,1 , DM

GUI

ツールの起動直後の初期状態は以下である (図 3.1)。

図 3.1: GUI ツール起動直後の状態

(13)

GUI

ツールでファイルの読み取りを行う際には、GUI ツールのメニューから File → Open

を選択し (図 3.2)、ファイル選択ダイアログから入力ファイルを 1 つ選択する (図 3.3)。

図 3.2: メニューから入力ファイルの選択

(14)

ファイル選択ダイアログの表示には JavaFX で標準で提供されている FileChooser クラ

スを使用した。ファイル選択ダイアログの表示は GUI の表示部分を担う ChartCreater クラ

スで行うが、ファイルに記述されているタスクスケジューリングのデータを読み取る部分

は ChartCreater クラスの readFile() メソッドで記述している。実装した部分を Listing3.2

に示す。

Listing 3.2:

ファイル選択ダイアログの表示

p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ... @ O v e r r i d e p u b l i c v o i d s t a r t ( S t a g e s t a g e ) { s t a g e . s e t T i t l e (" T a s k S c h e d u l i n g "); M e n u B a r m e n u B a r = new M e n u B a r (); Me n u m e n u F i l e = new M e n u (" F i l e "); M e n u I t e m o p e n = new M e n u I t e m (" O p e n "); op e n . s e t O n A c t i o n ( new E v e n t H a n d l e r < A c t i o n E v e n t >() { p u b l i c v o i d h a n d l e ( A c t i o n E v e n t t ) { o p e n ( s t a g e ); } }); ... p u b l i c v o i d o p e n ( S t a g e s t a g e ) { ... try { f i n a l F i l e C h o o s e r fc = new F i l e C h o o s e r (); i m p o r t F i l e = fc . s h o w O p e n D i a l o g ( s t a g e ); t a s k L i s t = r e a d F i l e ( i m p o r t F i l e ); } c a t c h ( E x c e p t i o n e ) { A l e r t f i l e O p e n E r r o r = new A l e r t ( A l e r t T y p e . E R R O R ); f i l e O p e n E r r o r . s e t T i t l e (" E r r o r "); f i l e O p e n E r r o r . s e t C o n t e n t T e x t (" C o u l d not o p e n a f i l e . " ) ; f i l e O p e n E r r o r . s h o w A n d W a i t (). i f P r e s e n t ( r e s p o n s e - > { if ( r e s p o n s e == B u t t o n T y p e . OK ) { r e t u r n ; } }); }

ChartCreater

クラスの readFile() メソッドは入力の CSV ファイルを 1 行ずつ読み取り、

カンマで区切られた各項目 (イベント時刻, タスク番号, ジョブ番号, イベントの種類) を

イベント情報を格納するクラス Event を作成して、それを ArrayList に格納している。同

時にデッドラインミスが発生した時刻のリスト、タスク番号のリストも作成している。

createTaskList()

メソッドの実装は Listing3.3 である。

(15)

Listing 3.3: TaskList

オブジェクトの作成

p u b l i c A r r a y L i s t < Event > r e a d F i l e ( F i l e f i l e ) t h r o w s I O E x c e p t i o n { F i l e R e a d e r f i l e R e a d e r = n u l l ;

B u f f e r e d R e a d e r b u f R e a d e r = n u l l ; S t r i n g s t r L i n e ;

A r r a y L i s t < Event > t a s k L i s t = new A r r a y L i s t < Event >() ; t a s k N o L i s t = new A r r a y L i s t < Integer >() ; d e a d l i n e M i s s T i m e L i s t = n u l l ; i n i t i a l i z e E v e n t T y p e L i s t () ; try { f i l e R e a d e r = new F i l e R e a d e r ( f i l e ) ; b u f R e a d e r = new B u f f e r e d R e a d e r ( f i l e R e a d e r ) ; w h i l e (( s t r L i n e = b u f R e a d e r . r e a d L i n e () ) != n u l l ) { S t r i n g [] l i n e = s t r L i n e . s p l i t ( D E L I M I T E R ) ; S t r i n g t i m e = l i n e [ 0 ] ; S t r i n g t a s k N o = l i n e [ 1 ] ; S t r i n g j o b N o = l i n e [ 2 ] ; S t r i n g e v e n t T y p e = l i n e [ 3 ] ; int i n t T i m e = j a v a . l a n g . I n t e g e r . p a r s e I n t ( t i m e ) ; int i n t E v e n t T y p e = ( e n u m E v e n t L i s t . get ( e v e n t T y p e ) ) . i n t V a l u e () ; E v e n t e v e n t = new E v e n t ( intTime , j a v a . l a n g . I n t e g e r . p a r s e I n t ( t a s k N o ) , j a v a . l a n g . I n t e g e r . p a r s e I n t ( j o b N o ) , ( e n u m E v e n t L i s t . get ( e v e n t T y p e ) ) . i n t V a l u e () ) ; t a s k L i s t . add ( e v e n t ) ; if ( d e a d l i n e M i s s T i m e L i s t == n u l l ) { d e a d l i n e M i s s T i m e L i s t = new A r r a y L i s t < Integer >() ; } if ( e v e n t . g e t E v e n t T y p e () == E v e n t . D M _ C O D E && ! d e a d l i n e M i s s T i m e L i s t . c o n t a i n s ( e v e n t . g e t T i m e () ) ) { d e a d l i n e M i s s T i m e L i s t . add ( e v e n t . g e t T i m e () ) ; } if (! t a s k N o L i s t . c o n t a i n s ( new I n t e g e r ( t a s k N o ) ) ) { t a s k N o L i s t . add ( new I n t e g e r ( t a s k N o ) ) ; } } C o l l e c t i o n s . s o r t ( d e a d l i n e M i s s T i m e L i s t ) ; C o l l e c t i o n s . s o r t ( t a s k N o L i s t ) ; } c a t c h ( I O E x c e p t i o n e ) { e . p r i n t S t a c k T r a c e () ; } f i n a l l y { f i l e R e a d e r . c l o s e () ; b u f R e a d e r . c l o s e () ; } r e t u r n t a s k L i s t ; }

(16)

3.3

グラフの描画

グラフの描画には stackoverflow に掲載されていたガントチャート表示のプログラム [6]

をカスタマイズした。stackoverflow に掲載されていたガントチャート表示は図 3.4 のよう

に系列と始点となる座標、終点となる座標を指定して長方形を描き、ガントチャートを作

成するものである。

図 3.4: stackoverflow のガントチャート表示プログラムによるサンプル [6]

今回作成する GUI で出力するグラフはタスク実行の CPU 使用時間を示す長方形だけで

なく、起動要求を示す上矢印、デッドラインを示す下矢印、デッドラインミスを示す×印

も表示する必要がある。そのため、Listing3.4 のように ExtraData クラスにイベントの種

類を示す type を追加した。Event クラスの変数 length は CPU 使用時間の長さ、styleClass

は描画に使用するスタイルシートの設定、type はイベントの種類を格納する変数である。

Listing 3.4: ExtraData

クラス

p u b l i c c l a s s E x t r a D a t a { p r i v a t e l o n g l e n g t h ; p r i v a t e S t r i n g s t y l e C l a s s ; p r i v a t e int t y p e ; p u b l i c s t a t i c f i n a l int E X E C = 0; p u b l i c s t a t i c f i n a l int S T A R T = 1; p u b l i c s t a t i c f i n a l int END = 2; p u b l i c s t a t i c f i n a l int D E A D L I N E _ M I S S = 3; p u b l i c E x t r a D a t a ( l o n g l e n g t h M s , S t r i n g s t y l e C l a s s , int t y p e ) { th i s . l e n g t h = l e n g t h M s ;

(17)

th i s . s t y l e C l a s s = s t y l e C l a s s ; th i s . t y p e = t y p e ; } p u b l i c l o n g g e t L e n g t h () { r e t u r n l e n g t h ; } p u b l i c v o i d s e t L e n g t h ( l o n g l e n g t h ) { th i s . l e n g t h = l e n g t h ; } p u b l i c S t r i n g g e t S t y l e C l a s s () { r e t u r n s t y l e C l a s s ; } p u b l i c v o i d s e t S t y l e C l a s s ( S t r i n g s t y l e C l a s s ) { th i s . s t y l e C l a s s = s t y l e C l a s s ; } p u b l i c int g e t T y p e () { r e t u r n t y p e ; } p u b l i c v o i d s e t T y p e ( int t y p e ){ th i s . t y p e = t y p e ; } }

グラフの描画は ChartCreater クラスの createChart() メソッドで行われる。ChartCreater

クラスの open() メソッドで同じく ChartCreater クラスの readFile() メソッドを呼び出し

てデータを読み込み、イベントのリストを ArrayList を作成した (Listing3.2) 後、リスト

からイベントのデータを一つずつ取り出し、イベント内容に合わせた描画を行うが、実際

の描画を行う前に、TaskList から読み込んだデータを描画用のデータに変換する部分が

必要になる。イベントのデータを描画データとして格納するのが先程述べた Listing3.4 の

ExtraData

クラスである。 タスクの実行の CPU 使用時間を四角で描画するためのデータ

格納は Listing3.5 である。開始時刻を起点として終了時刻–開始時刻の長さの四角を描く

という形でデータを格納する。

Listing 3.5:

タスクの実行の CPU 使用時間の描画データへの変換

p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ...

p u b l i c v o i d c r e a t e C h a r t ( int minTime , int maxTime , S t a g e s t a g e ) { ...

for ( int i = s t a r t I n d e x ; i <= e n d I n d e x ; i ++) { ...

(18)

c a s e ( E v e n t . S T _ C O D E ) : P r o c e s s p = new P r o c e s s ( e v e n t . g e t T a s k N o () , e v e n t . g e t J o b N o () ) ; i n F l i g h t . put ( p . g e t K e y () , new I n t e g e r ( e v e n t . g e t T i m e () ) ) ; b r e a k ; c a s e ( E v e n t . E D _ C O D E ) : P r o c e s s p r o c = new P r o c e s s ( e v e n t . g e t T a s k N o () , e v e n t . g e t J o b N o () ) ; int s t a r t T i m e = m i n T i m e ; if ( i n F l i g h t . get ( p r o c . g e t K e y () ) != n u l l ) { s t a r t T i m e = ( i n F l i g h t . get ( p r o c . g e t K e y () ) ) . i n t V a l u e () ; i n F l i g h t . r e m o v e ( p r o c . g e t K e y () ) ; } int e n d T i m e = e v e n t . g e t T i m e () ;

s e r i e s . g e t D a t a () . add ( new X Y C h a r t . Data < Number , String >( s t a r t T i m e , machine , new E x t r a D a t a ( e n d T i m e -s t a r t T i m e , " -statu-s - g r a y " , E x t r a D a t a . E X E C ) ) ) ; b r e a k ; } s e r i e s A r r a y . put ( e v e n t . g e t T a s k N o () - 1 , s e r i e s ) ; ...

同様に、タスクの起動要求、デッドラインやデッドラインミスの描画データを作成してい

る部分は以下となる (Listing3.6)。タスクの実行の CPU 使用時間の描画 (Listing3.5) と分

けて描画しているのは、タスクの起動要求、デッドラインやデッドラインミスを後から描

画することで、タスクの実行の CPU 使用時間と重なっても、タスクの実行の CPU 使用

時間よりも前面に表示されるようにするためである。

Listing 3.6:

タスクの起動要求、デッドラインやデッドラインミスの描画データへの変換

for ( int i = s t a r t I n d e x ; i <= e n d I n d e x ; i ++) { E v e n t e v e n t = t a s k L i s t . get ( i ) ; X Y C h a r t . S e r i e s s e r i e s = s e r i e s A r r a y . get ( e v e n t . g e t T a s k N o () -1) ; S t r i n g m a c h i n e = " T a s k " + ( e v e n t . g e t T a s k N o () ) ; if ( s e r i e s == n u l l ) { s e r i e s = new X Y C h a r t . S e r i e s () ; m a c h i n e L i s t . put ( e v e n t . g e t T a s k N o () - 1 , m a c h i n e ) ; } int e v e n t T y p e = e v e n t . g e t E v e n t T y p e () ; if ( m a x T i m e < e v e n t . g e t T i m e () ) { m a x T i m e = e v e n t . g e t T i m e () ; } s w i t c h ( e v e n t T y p e ) { c a s e ( E v e n t . R L _ C O D E ) :

s e r i e s . g e t D a t a () . add ( new X Y C h a r t . Data < Number , String >( e v e n t . g e t T i m e () , machine , new E x t r a D a t a ( e v e n t . g e t T i m e () , " status - b l a c k " , E x t r a D a t a . S T A R T ) ) ) ; b r e a k ;

(19)

c a s e ( E v e n t . D L _ C O D E ) :

s e r i e s . g e t D a t a () . add ( new X Y C h a r t . Data < Number , String >( e v e n t . g e t T i m e () , machine , new E x t r a D a t a ( e v e n t . g e t T i m e () , " status - b l a c k " , E x t r a D a t a . D E A D L I N E ) ) ) ; b r e a k ;

c a s e ( E v e n t . D M _ C O D E ) :

s e r i e s . g e t D a t a () . add ( new X Y C h a r t . Data < Number , String >( e v e n t . g e t T i m e () , machine , new E x t r a D a t a ( e v e n t . g e t T i m e () , " d e a d l i n e - m i s s " , E x t r a D a t a . D E A D L I N E _ M I S S ) ) ) ; b r e a k ; } s e r i e s A r r a y . put ( e v e n t . g e t T a s k N o () - 1 , s e r i e s ) ; } ... for ( int i = 0; i < s e r i e s A r r a y . s i z e () ; i ++) { c h a r t . g e t D a t a () . add ( s e r i e s A r r a y . get ( i ) ) ; }

グラフの軸や系列部分の描画データは Listing3.7 のように作成した。

Listing 3.7:

グラフの軸や系列の描画データ作成

N u m b e r A x i s x A x i s = n u l l ; if ( m a x T i m e - m i n T i m e > 2 0 0 0 ) {

x A x i s = new N u m b e r A x i s ( minTime , maxTime , 10) ; } e l s e {

x A x i s = new N u m b e r A x i s ( minTime , maxTime , 1) ; } x A x i s . s e t L a b e l ( " " ) ; x A x i s . s e t T i c k L a b e l F i l l ( C o l o r . B L A C K ) ; x A x i s . s e t M i n o r T i c k C o u n t (1) ; x A x i s . s e t A u t o R a n g i n g ( f a l s e ) ; f i n a l C a t e g o r y A x i s y A x i s = new C a t e g o r y A x i s () ; c h a r t = new G a n t t C h a r t < >( xAxis , y A x i s ) ; y A x i s . s e t L a b e l ( " " ) ; y A x i s . s e t T i c k L a b e l F i l l ( C o l o r . B L A C K ) ; c h a r t . s e t T i t l e (" T a s k S c h e d u l i n g ") ; c h a r t . s e t L e g e n d V i s i b l e ( f a l s e ) ; c h a r t . s e t B l o c k H e i g h t ( 5 0 ) ; A r r a y L i s t t a s k N o L i s t = g e t T a s k N o L i s t () ; c h a r t . s e t M i n H e i g h t ( 1 2 0 * t a s k N o L i s t . s i z e () ) ; c h a r t . g e t S t y l e s h e e t s () . add ( g e t C l a s s () . g e t R e s o u r c e (" g a n t t c h a r t . css ") . t o E x t e r n a l F o r m () ) ; A r r a y L i s t < String > y A x i s C a t e g o r y L i s t = new A r r a y L i s t () ; for ( int i = 0; i < t a s k N o L i s t . s i z e () ; i ++) { y A x i s C a t e g o r y L i s t . add (" T a s k " + ( i + 1) ) ; if ( s e r i e s A r r a y . get ( i ) != n u l l ) { c h a r t . g e t D a t a () . add ( s e r i e s A r r a y . get ( i ) ) ;

(20)

} e l s e { c h a r t . g e t D a t a () . add ( new X Y C h a r t . S e r i e s () ) ; } } y A x i s . s e t A u t o R a n g i n g ( f a l s e ) ; y A x i s . s e t C a t e g o r i e s ( F X C o l l e c t i o n s . < String > o b s e r v a b l e A r r a y L i s t ( y A x i s C a t e g o r y L i s t ) ) ; y A x i s . i n v a l i d a t e R a n g e ( y A x i s C a t e g o r y L i s t ) ;

この描画データを元に実際に描画を行うのは GanttChart クラスである。GanttChart ク

ラスも stackoverflow に掲載されていたもの [6] を今回開発した GUI ツール用にカスタマ

イズして使用している。GanttChart クラスは JavaFX で提供されている XYChart を継承

しており、layoutPlotChildren() メソッドでガントチャートの四角形の描画を行っている。

GanttChart

クラスの layoutPlotChildren() メソッドは stackoverflow に掲載されていたも

のではガントチャートの四角形のみの描画だったが、カスタマイズを行い、ExtraData ク

ラスに格納したイベントの種類や描画データを使用し、タスクの実行の CPU 使用時間・

タスクの起動要求・デッドラインやデッドラインミスの描画を行うよう変更した。タスク

の起動要求を示す上矢印、デッドラインを示す下矢印、デッドラインミスを示す×印は

javafx.scene.shape.Polygon

クラスを使用して描画している。Polygon クラスを使用して図

形の描画を行う際には図形の各頂点の座標を指定する。Listing3.8 は上矢印の図形の描画

部分である。

Listing 3.8: Polygon

クラスを使用した上矢印の描画

D o u b l e [] a r r o w S h a p e = new D o u b l e [ ] { 0 d , -50 d , 5 d , -35 d , 1 d , -35 d , 1 d , -10 d , -1 d , -10 d , -1 d , -35 d , -5 d , -35 d }; P o l y g o n p o l y g o n = new P o l y g o n () ; p o l y g o n . g e t P o i n t s () . a d d A l l ( a r r o w S h a p e ) ;

実際にイベントの種類に応じた図形の描画を行っているのが GanttChart クラスの

lay-outPlotChildren()

メソッドで、実装は Listing3.9 である。

Listing 3.9:

グラフの軸や系列の描画データ作成

p u b l i c c l a s s G a n t t C h a r t < X , Y > e x t e n d s XYChart < X , Y > { ... @ O v e r r i d e p r o t e c t e d v o i d l a y o u t P l o t C h i l d r e n () { for ( int s e r i e s I n d e x = 0; s e r i e s I n d e x < g e t D a t a () . s i z e () ; s e r i e s I n d e x ++) { Series < X , Y > s e r i e s = g e t D a t a () . get ( s e r i e s I n d e x ) ; I t e r a t o r < Data < X , Y > > i t e r = g e t D i s p l a y e d D a t a I t e r a t o r ( s e r i e s ) ; w h i l e ( i t e r . h a s N e x t () ) {

(21)

Data < X , Y > i t e m = i t e r . n e x t () ; d o u b l e x = g e t X A x i s () . g e t D i s p l a y P o s i t i o n ( i t e m . g e t X V a l u e () ) ; d o u b l e y = g e t Y A x i s () . g e t D i s p l a y P o s i t i o n ( i t e m . g e t Y V a l u e () ) ; if ( D o u b l e . i s N a N ( x ) || D o u b l e . i s N a N ( y ) ) { c o n t i n u e ; } N o d e b l o c k = i t e m . g e t N o d e () ; int t y p e = (( E x t r a D a t a ) ( i t e m . g e t E x t r a V a l u e () ) ) . g e t T y p e () ; R e c t a n g l e e l l i p s e ; if ( b l o c k != n u l l ) { if ( b l o c k i n s t a n c e o f S t a c k P a n e ) { S t a c k P a n e r e g i o n = ( S t a c k P a n e ) i t e m . g e t N o d e () ; if ( t y p e == E x t r a D a t a . E X E C ) { if ( r e g i o n . g e t S h a p e () == n u l l ) { e l l i p s e = new R e c t a n g l e ( g e t L e n g t h ( i t e m . g e t E x t r a V a l u e () ) , g e t B l o c k H e i g h t () ) ; } e l s e if ( r e g i o n . g e t S h a p e () i n s t a n c e o f R e c t a n g l e ) { e l l i p s e = ( R e c t a n g l e ) r e g i o n . g e t S h a p e () ; } e l s e { r e t u r n ; } e l l i p s e . s e t W i d t h ( g e t L e n g t h ( i t e m . g e t E x t r a V a l u e () ) * (( g e t X A x i s () i n s t a n c e o f N u m b e r A x i s ) ? M a t h . abs ((( N u m b e r A x i s ) g e t X A x i s () ) . g e t S c a l e () ) : 1) ) ; e l l i p s e . s e t H e i g h t ( g e t B l o c k H e i g h t () * (( g e t Y A x i s () i n s t a n c e o f N u m b e r A x i s ) ? M a t h . abs ((( N u m b e r A x i s ) g e t Y A x i s () ) . g e t S c a l e () ) : 1) ) ; ... r e g i o n . s e t S h a p e ( n u l l ) ; r e g i o n . s e t S h a p e ( e l l i p s e ) ; r e g i o n . s e t S c a l e S h a p e ( f a l s e ) ; r e g i o n . s e t C e n t e r S h a p e ( f a l s e ) ; r e g i o n . s e t C a c h e S h a p e ( f a l s e ) ; b l o c k . s e t L a y o u t X ( x ) ; b l o c k . s e t L a y o u t Y ( y - g e t B l o c k H e i g h t () ) ; } e l s e if ( t y p e == E x t r a D a t a . S T A R T ) { D o u b l e [] a r r o w S h a p e = new D o u b l e [ ] { 0 d , -50 d , 5 d , -35 d , 1 d , -35 d , 1 d , -10 d , -1 d , -10 d , -1 d , -35 d , -5 d , -35 d }; P o l y g o n p o l y g o n = new P o l y g o n () ; p o l y g o n . g e t P o i n t s () . a d d A l l ( a r r o w S h a p e ) ; r e g i o n . s e t S h a p e ( p o l y g o n ) ; r e g i o n . s e t S c a l e S h a p e ( f a l s e ) ;

(22)

r e g i o n . s e t C e n t e r S h a p e ( f a l s e ) ; r e g i o n . s e t C a c h e S h a p e ( f a l s e ) ; b l o c k . s e t L a y o u t X ( x ) ; b l o c k . s e t L a y o u t Y ( y ) ; } e l s e if ( t y p e == E x t r a D a t a . END ) { D o u b l e [] a r r o w S h a p e = new D o u b l e []{0.0 , 0.0 , -5.0 , -15.0 , -1.0 , -15.0 , -1.0 , -40.0 , 1.0 , -40.0 , 1.0 , -15.0 , 5.0 , -15.0}; P o l y g o n p o l y g o n = new P o l y g o n () ; p o l y g o n . g e t P o i n t s () . a d d A l l ( a r r o w S h a p e ) ; r e g i o n . s e t S h a p e ( p o l y g o n ) ; r e g i o n . s e t S c a l e S h a p e ( f a l s e ) ; r e g i o n . s e t C e n t e r S h a p e ( f a l s e ) ; r e g i o n . s e t C a c h e S h a p e ( f a l s e ) ; b l o c k . s e t L a y o u t X ( x ) ; b l o c k . s e t L a y o u t Y ( y ) ; } e l s e if ( t y p e == E x t r a D a t a . D E A D L I N E _ M I S S ) { D o u b l e [] c r o s s S h a p e = new D o u b l e [ ] { 0 d , 2 d , 8 d , 10 d , 10 d , 8 d , 2 d , 0 d , 10 d , -8 d , 8 d , -10 d , 0 d , -2 d , -8 d , -10 d , -10 d , -8 d , -2 d , 0 d , -10 d , 8 d , -8 d , 10 d }; P o l y g o n p o l y g o n = new P o l y g o n () ; p o l y g o n . g e t P o i n t s () . a d d A l l ( c r o s s S h a p e ) ; r e g i o n . s e t S h a p e ( p o l y g o n ) ; r e g i o n . s e t S c a l e S h a p e ( f a l s e ) ; r e g i o n . s e t C e n t e r S h a p e ( f a l s e ) ; r e g i o n . s e t C a c h e S h a p e ( f a l s e ) ; b l o c k . s e t L a y o u t X ( x ) ; b l o c k . s e t L a y o u t Y ( y ) ; } } } ... }

3.4

チャートを部分表示する機能の実装

入力ファイルは 10000 ティック程度までの長いデータを想定しており、一度に全部のデー

タを表示すると見づらくなる場合が考えられるので、表示するティックの範囲を区切って

表示を行う部分表示の機能が必要となる。部分表示を行うためには表示するティックの範

囲に該当するイベントをリストから検索する必要がある。リスト内にはイベントがティッ

ク順に格納されているので、表示範囲の最初のティック以降の先頭のイベントと表示範

囲の最後のティック以前の最後のイベントを検索することで、部分表示に必要なイベント

のリスト内でのインデックスの範囲を取得することができる。表示範囲の最初のティック

(23)

以降の先頭のイベントの検索は ChartCreater クラスの binarySearchMinIndex() メソッド

(Listing3.10

参照) で、表示範囲の最後のティック以前の最後のイベントは ChartCreater

クラスの binarySearchMaxIndex() メソッド (Listing3.11 参照) で実装した。

Listing 3.10:

表示範囲の最初のティック以降の先頭のイベントの検索

p u b l i c int b i n a r y S e a r c h M i n I n d e x ( int m i n T i m e ) { int l e f t = 0; int r i g h t = t a s k L i s t . s i z e () - 1; E v e n t e v e n t = n u l l ; int c u r r e n t I n d e x = 0; w h i l e ( t r u e ) { c u r r e n t I n d e x = ( int ) ( l e f t + r i g h t ) / 2; e v e n t = t a s k L i s t . get ( c u r r e n t I n d e x ) ; if ( e v e n t . g e t T i m e () > m i n T i m e ) { if ( t a s k L i s t . get ( c u r r e n t I n d e x - 1) . g e t T i m e () < m i n T i m e ) { b r e a k ; } r i g h t = c u r r e n t I n d e x ; } e l s e if ( e v e n t . g e t T i m e () < m i n T i m e ) { l e f t = c u r r e n t I n d e x ; } e l s e { b r e a k ; } } w h i l e ( t r u e ) { if ( t a s k L i s t . get ( c u r r e n t I n d e x ) . g e t T i m e () >= m i n T i m e && t a s k L i s t . get ( c u r r e n t I n d e x - 1) . g e t T i m e () < m i n T i m e ) { b r e a k ; } c u r r e n t I n d e x - -; } r e t u r n c u r r e n t I n d e x ; }

(24)

Listing 3.11:

表示範囲の最後のティック以前の最後のイベントの検索

p u b l i c int b i n a r y S e a r c h M a x I n d e x ( int m a x T i m e ) { int l e f t = 0; int r i g h t = t a s k L i s t . s i z e () - 1; E v e n t e v e n t = n u l l ; int c u r r e n t I n d e x = 0; w h i l e ( t r u e ) { c u r r e n t I n d e x = ( int ) ( l e f t + r i g h t ) / 2; e v e n t = t a s k L i s t . get ( c u r r e n t I n d e x ) ; if ( e v e n t . g e t T i m e () > m a x T i m e ) { r i g h t = c u r r e n t I n d e x ; } e l s e if ( e v e n t . g e t T i m e () < m a x T i m e ) { if ( t a s k L i s t . get ( c u r r e n t I n d e x + 1) . g e t T i m e () > m a x T i m e ) { b r e a k ; } l e f t = c u r r e n t I n d e x ; } e l s e { b r e a k ; } } w h i l e ( t r u e ) { if ( t a s k L i s t . get ( c u r r e n t I n d e x ) . g e t T i m e () <= m a x T i m e && t a s k L i s t . get ( c u r r e n t I n d e x + 1) . g e t T i m e () > m a x T i m e ) { b r e a k ; } c u r r e n t I n d e x ++; } r e t u r n c u r r e n t I n d e x ; }

入力ファイル内の最後のイベントの時刻が 50 を超える場合、初期表示は先頭から 50

ティックとし、メニューから Range → Next50 を選択することで、次の 50 ティックを表

示できるようにした (図 3.5 参照)。前に戻る場合はメニューから Range → Prev50 を選択

することで、前の 50 ティックを表示できるようにした (図 3.6 参照)。現在の先頭のティッ

クが 50 未満にもかかわらず、前 50 ティックの表示が呼び出された場合は、先頭から 50

ティックを表示する。最後のティックから数えて 50 未満のティックであるにもかかわず、

次の 50 ティックの表示が呼び出された場合は、最後の 50 ティックを表示する。全体が 50

ティックに満たない場合は全体を表示する。また、50 ティック区切りによる表示だけでな

く、Range → Show All を選択して部分表示から全体表示へ切り替える機能 (図 3.7 参照)

や、Range → Specify Range を選択し、任意の範囲を表示できる機能も実装した (図 3.8 参

照)。範囲指定によるグラフの表示結果は図 3.9 である。

(25)

図 3.5: 次 50 件表示のメニュー選択

(26)

図 3.7: 全体表示のメニュー選択

(27)

図 3.9: ティックの範囲を指定したグラフの表示結果

チャートを表示する際に、タスク実行の CPU 使用時間を表示するためには、タスク実

行の開始時刻と終了時刻をマッチさせる必要がある。この役割を担うために本 GUI ツー

ルでは HashMap を使用し、タスク実行の開始のイベントがあった場合に HashMap にレ

コードを追加し、タスク実行の終了のイベントがあった場合に HashMap にタスク番号と

ジョブ番号をキーにタスク実行開始のレコードを HashMap に取得しに行き、そのレコー

ドを読み取った後 HashMap から削除する動作になっている。しかし、チャートの部分表

示を行う場合にはタスク実行の開始時刻が表示範囲に含まれるがタスク実行の終了時刻

が表示範囲に含まれない場合、逆にタスク実行の終了時刻は表示範囲に含まれるが、タス

ク実行の開始時刻が表示範囲に含まれない場合が生じてくる。タスク実行終了のイベント

に対応するタスク実行開始のイベントが表示範囲に存在しない場合、タスク実行の開始

時刻を表示範囲の最初のティックとしてチャートを表示する。タスク実行の終了のイベン

トを読み取った際に、タスク実行開始のレコードを HashMap から消しているので、タス

ク実行の開始のイベントに対応するタスク実行の終了時刻がない場合は、HashMap にタ

スク実行開始のレコードが残ることになる。この場合、削除されずに残ったタスク実行開

始のレコードに対してはタスク実行の終了時刻として表示範囲の最後のティックを設定し

チャートに表示する。図 3.12 の inFlight という HashMap がタスク番号とジョブ番号を結

合した文字列をキーにタスク実行開始のレコードを格納する HashMap である。

(28)

Listing 3.12:

タスク実行開始時刻とタスク実行終了時刻のマッチング

p u b l i c v o i d c r e a t e C h a r t ( int minTime , int maxTime , S t a g e s t a g e ) {

... i n F l i g h t = new H a s h M a p () ; ... for ( int i = s t a r t I n d e x ; i <= e n d I n d e x ; i ++) { ... s w i t c h ( e v e n t T y p e ) { c a s e ( E v e n t . S T _ C O D E ) : P r o c e s s p = new P r o c e s s ( e v e n t . g e t T a s k N o () , e v e n t . g e t J o b N o () ) ; i n F l i g h t . put ( p . g e t K e y () , new I n t e g e r ( e v e n t . g e t T i m e () ) ) ; b r e a k ; c a s e ( E v e n t . E D _ C O D E ) : P r o c e s s p r o c = new P r o c e s s ( e v e n t . g e t T a s k N o () , e v e n t . g e t J o b N o () ) ; int s t a r t T i m e = m i n T i m e ; if ( i n F l i g h t . get ( p r o c . g e t K e y () ) != n u l l ) { s t a r t T i m e = ( i n F l i g h t . get ( p r o c . g e t K e y () ) ) . i n t V a l u e () ; i n F l i g h t . r e m o v e ( p r o c . g e t K e y () ) ; } int e n d T i m e = e v e n t . g e t T i m e () ;

s e r i e s . g e t D a t a () . add ( new X Y C h a r t . Data < Number , String >( s t a r t T i m e , machine , new E x t r a D a t a ( e n d T i m e -s t a r t T i m e , " -statu-s - g r a y " , E x t r a D a t a . E X E C ) ) ) ; b r e a k ; } s e r i e s A r r a y . put ( e v e n t . g e t T a s k N o () - 1 , s e r i e s ) ; } if ( i n F l i g h t . s i z e () > 0) { Set < String > p r o c K e y S e t = i n F l i g h t . k e y S e t () ; I t e r a t o r < String > p r o c K e y I t r = p r o c K e y S e t . i t e r a t o r () ; w h i l e ( p r o c K e y I t r . h a s N e x t () ) { S t r i n g p r o c K e y = p r o c K e y I t r . n e x t () ; S t r i n g [] p r o c e s s = p r o c K e y . s p l i t (" -") ; int t a s k N o = I n t e g e r . p a r s e I n t ( p r o c e s s [ 0 ] ) ; int j o b N o = I n t e g e r . p a r s e I n t ( p r o c e s s [ 1 ] ) ; int s t a r t T i m e = ( i n F l i g h t . get ( p r o c K e y ) ) . i n t V a l u e () ; int e n d T i m e = m a x T i m e ; S t r i n g m a c h i n e = " T a s k " + t a s k N o ;

s e r i e s A r r a y . get ( t a s k N o - 1) . g e t D a t a () . add ( new X Y C h a r t . Data < Number , String >( s t a r t T i m e , machine , new

E x t r a D a t a ( e n d T i m e - s t a r t T i m e , " status - g r a y " , E x t r a D a t a . E X E C ) ) ) ;

} } ...

(29)

3.5

スクロール・バーの表示とズームイン

/

ズームアウト機

能の実装

今回の GUI ツールでは初期表示のサイズを 500 ピクセル× 500 ピクセルとし、作成し

たグラフがそのサイズを超える場合は自動的にスクロール・バーを表示する動作とした。

また、図 3.10 のようにグラフの任意の箇所を右クリックし、Zoom In または Zoom Out を

選択することにより、任意の場所でズームイン/ズームアウトができるように実装を行っ

た。

図 3.10: ズームイン/ズームアウトの選択

スクロール・バーの表示には ScrollPane クラスを使用し、ズームの実装は Pixel Duke[7] に

掲載されていた方法を参照し、スクロール対象のコンテンツを Group に入れて Group を

ScrollPane

のコンテンツとして設定することで、ScrollPane 内の全てのコンポーネントを

ズームイン/ズームアウトできるようにした。スクロール・バーの表示は以下 Listing3.13

のように行っている。

(30)

Listing 3.13:

スクロール・バーの表示

p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ... S c r o l l P a n e sp = new S c r o l l P a n e () ; G r o u p g r o u p = new G r o u p () ; ... @ O v e r r i d e p u b l i c v o i d s t a r t ( S t a g e s t a g e ) { ... sp . s e t C o n t e n t ( g r o u p ) ; S c e n e s c e n e = new S c e n e ( new V B o x () , 500 , 5 0 0 ) ; (( V B o x ) s c e n e . g e t R o o t () ) . g e t C h i l d r e n () . a d d A l l ( menuBar , sp ) ; s t a g e . s e t S c e n e ( s c e n e ) ; s t a g e . s h o w () ; } ... p u b l i c v o i d o p e n ( S t a g e s t a g e ) { ... g r o u p . g e t C h i l d r e n () . add ( c h a r t ) ; sp . r e q u e s t L a y o u t () ; ... }

ズームイン/ズームアウトについてはチャートの表示を行う ChartCreater クラスの

creat-eChart()

メソッドで Listing3.14 のように実装を行い、チャートの横幅を 1.25 倍にするこ

とでズームイン、チャートの横幅を 0.8 倍にすることでズームアウトを実現している。

Listing 3.14:

ズームイン/ズームアウトの実装

p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ...

p u b l i c v o i d c r e a t e C h a r t ( int minTime , int maxTime , S t a g e s t a g e ) { ... f i n a l C o n t e x t M e n u c o n t e x t M e n u = new C o n t e x t M e n u () ; M e n u I t e m z o o m I n = new M e n u I t e m (" Z o o m In ") ; M e n u I t e m z o o m O u t = new M e n u I t e m (" Z o o m Out ") ; c h a r t . a d d E v e n t H a n d l e r ( M o u s e E v e n t . M O U S E _ C L I C K E D , new E v e n t H a n d l e r < M o u s e E v e n t >() { @ O v e r r i d e p u b l i c v o i d h a n d l e ( M o u s e E v e n t e ) { if ( e . g e t B u t t o n () == M o u s e B u t t o n . S E C O N D A R Y ) { c o n t e x t M e n u . s h o w ( chart , e . g e t S c r e e n X () , e . g e t S c r e e n Y () ) ; } } }) ; c o n t e x t M e n u . g e t I t e m s () . add ( z o o m I n ) ; c o n t e x t M e n u . g e t I t e m s () . add ( z o o m O u t ) ; g r o u p . g e t C h i l d r e n () . add ( c h a r t ) ; sp . s e t C o n t e n t ( g r o u p ) ;

(31)

sp . r e q u e s t L a y o u t () ; z o o m I n . s e t O n A c t i o n ( new E v e n t H a n d l e r < A c t i o n E v e n t >() { p u b l i c v o i d h a n d l e ( A c t i o n E v e n t t ) { c h a r t . s e t P r e f W i d t h ( c h a r t . g e t W i d t h () * 1 . 2 5 ) ; sp . r e q u e s t L a y o u t () ; } }) ; z o o m O u t . s e t O n A c t i o n ( new E v e n t H a n d l e r < A c t i o n E v e n t >() { p u b l i c v o i d h a n d l e ( A c t i o n E v e n t t ) { c h a r t . s e t P r e f W i d t h ( c h a r t . g e t W i d t h () * 0 . 8 ) ; sp . r e q u e s t L a y o u t () ; } }) ; } ...

実際にズームインを行った場合の画面ショットが以下である。図 3.11 がズーム前、図

3.12

がズーム後の画面ショットである。

図 3.11: ズームイン前の画面ショット

図 3.12: ズームイン後の画面ショット

3.6

チャートを画像ファイルとしてエクスポートする機能の

実装

画像ファイルのエクスポートには JavaFX で提供されている Node クラスの snapshot()

メソッド [8] を使用した。GanttChart クラスは XYChart クラスを継承しており、XYChart

クラスが Node クラスを継承しているので、GanttChart クラスからこのメソッドを呼び出

すことができる。なお、実装の際には Oracle 社のチュートリアル [9] や stackoverflow[10]

のサンプルを参照した。今回の GUI ツールでは入力ファイルを開き、チャートを表示し

(32)

た状態でメニューバーから File → Export を選択することで表示しているチャートを png

形式の画像ファイルとしてエクスポートできる。本 GUI ツールでは非常に長いティック

数の画像を数十回程度ズームしてからダウンロードするような大きな描画データをエク

スポートする場合も想定し、Platform.runLater() メソッドを使用して画像エクスポート

を行うスレッドを JavaFX アプリケーション本体のスレッドと別にしている。図 3.13 がそ

の画面ショットである。

図 3.13: チャートを画像ファイルとしてエクスポート

保存先のファイルの選択には、入力ファイルの読み取りの際と同様 FileChooser クラス

を使用し、ダイアログを表示して保存先のディレクトリを GUI で選択させ、ファイル名

を入力できるようにした。また、拡張子の付け忘れや入力ミスなく必ず png ファイルとし

て保存するよう拡張子フィルタを追加した。図 3.14 が画像エクスポートの際に表示され

る拡張子フィルタをつけたファイル選択ダイアログである。エクスポートのメニューの表

示は ChartCreater クラスの start() メソッドで、エクスポート処理の実装は ChartCreater

クラスの export() メソッドで行っている。実装は Listing3.15 のとおりである。

(33)

図 3.14: 画像エクスポート時に表示される拡張子フィルタをつけたファイル選択ダイア

ログ

Listing 3.15:

画像ファイルとしてのエクスポートの実装

p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ... @ O v e r r i d e p u b l i c v o i d s t a r t ( S t a g e s t a g e ) { ... M e n u B a r m e n u B a r = new M e n u B a r () ; Me n u m e n u F i l e = new M e n u (" F i l e ") ; ... M e n u I t e m d o w n l o a d = new M e n u I t e m (" E x p o r t ") ; d o w n l o a d . s e t O n A c t i o n ( new E v e n t H a n d l e r < A c t i o n E v e n t >() { p u b l i c v o i d h a n d l e ( A c t i o n E v e n t t ) { T h r e a d t h r e a d = new T h r e a d (() - > { P l a t f o r m . r u n L a t e r (() - > e x p o r t ( s t a g e ) ) ; }) ; t h r e a d . s t a r t () ; } }) ; m e n u F i l e . g e t I t e m s () . a d d A l l ( open , d o w n l o a d ) ; m e n u B a r . g e t M e n u s () . a d d A l l ( m e n u F i l e ) ; ... } ... p u b l i c v o i d e x p o r t ( S t a g e s t a g e ) { F i l e C h o o s e r fc = new F i l e C h o o s e r () ; F i l e C h o o s e r . E x t e n s i o n F i l t e r e x t F i l t e r = new F i l e C h o o s e r . E x t e n s i o n F i l t e r (" PNG f i l e s (*. png ) " , "*. png ") ; fc . g e t E x t e n s i o n F i l t e r s () . add ( e x t F i l t e r ) ; fc . s e t T i t l e (" S a v e I m a g e ") ; Fi l e f i l e = fc . s h o w S a v e D i a l o g ( s t a g e ) ; W r i t a b l e I m a g e i m a g e = c h a r t . s n a p s h o t ( new S n a p s h o t P a r a m e t e r s () , n u l l ) ;

(34)

try { I m a g e I O . w r i t e ( S w i n g F X U t i l s . f r o m F X I m a g e ( image , n u l l ) , " png " , fi l e ) ; } c a t c h ( I O E x c e p t i o n e ) { e . p r i n t S t a c k T r a c e () ; } }

エクスポートを行う際には、現在表示されているチャートがそのままエクスポートされ

る。表示範囲やズーム回数なども反映してエクスポートを行うことができる。実際に表示

範囲として 6700∼6730 ティックを指定し、ズームを 2 回行った状態でエクスポートした

画像が図 3.15 である。

図 3.15: 表示範囲を指定しズームしてエクスポートした画像

3.7

デッドラインミスを検索する機能の実装

デッドラインミスの検索は前方と後方の 2 方向での検索が可能になるよう実装した。

チャートを開いた状態でメニューから Deadline Miss → Previous で前方への検索、Deadline

Miss

→ Next で後方への検索を行うことができる (図 3.16)。検索を行った結果、デッドラ

インミスは図 3.17 のように表示される。

デッドラインミスの検索を行う前に、入力ファイルを選択してチャートが表示されてい

る必要がある。入力ファイルが選択されておらず、チャートがまだ表示されていない場合

には図 3.18 のエラーメッセージが表示される。図 3.16 のメニュー表示及び入力ファイル

未選択のエラー表示の実装部分は以下 Listing3.16 である。

(35)

図 3.16: デッドラインミス検索のメニュー 図 3.17: 検索されたデッドラインミスの表示

図 3.18: 入力ファイル未選択のエラー表示

Listing 3.16:

デッドラインミス検索のメニュー表示の実装

... p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ... @ O v e r r i d e p u b l i c v o i d s t a r t ( S t a g e s t a g e ) { ... Me n u m e n u D e a d l i n e M i s s = new M e n u (" D e a d l i n e M i s s ") ; M e n u I t e m n e x t = new M e n u I t e m (" N e x t ") ; ne x t . s e t O n A c t i o n ( new E v e n t H a n d l e r < A c t i o n E v e n t >() { p u b l i c v o i d h a n d l e ( A c t i o n E v e n t t ) { try { ne x t ( s t a g e ) ; } c a t c h ( E x c e p t i o n e ) { A l e r t c h a r t N u l l = new A l e r t ( A l e r t T y p e . E R R O R ) ; c h a r t N u l l . s e t T i t l e (" E r r o r ") ; c h a r t N u l l . s e t C o n t e n t T e x t (" O p e n a f i l e f i r s t .") ; c h a r t N u l l . s h o w A n d W a i t () . i f P r e s e n t ( r e s p o n s e - > { if ( r e s p o n s e == B u t t o n T y p e . OK ) { r e t u r n ; } }) ; } }

(36)

}) ; M e n u I t e m p r e v i o u s = new M e n u I t e m (" P r e v i o u s ") ; p r e v i o u s . s e t O n A c t i o n ( new E v e n t H a n d l e r < A c t i o n E v e n t >() { p u b l i c v o i d h a n d l e ( A c t i o n E v e n t t ) { try { p r e v i o u s ( s t a g e ) ; } c a t c h ( E x c e p t i o n e ) { A l e r t c h a r t N u l l = new A l e r t ( A l e r t T y p e . E R R O R ) ; c h a r t N u l l . s e t T i t l e (" E r r o r ") ; c h a r t N u l l . s e t C o n t e n t T e x t (" O p e n a f i l e f i r s t .") ; c h a r t N u l l . s h o w A n d W a i t () . i f P r e s e n t ( r e s p o n s e - > { if ( r e s p o n s e == B u t t o n T y p e . OK ) { r e t u r n ; } }) ; } } }) ; ...

3.2

節で記述した入力ファイルの読み取りの部分でデッドラインミスが発生した時刻の重

複を排除した昇順のリストをあらかじめ作成しておく。これによって現在グラフに描画さ

れてい部分にデッドラインミスが発生していなくても、デッドラインミスの発生箇所に

ジャンプすることができる。メニューから Deadline Miss → Next を選択した際に呼び出

される ChartCreater クラスの next() メソッドの実装は以下 Listing3.17 である。前述した

入力ファイルが未選択の場合のエラー処理もここに記述されている。デッドラインミスが

発生した時刻のリストを参照し、次のデッドラインの時刻を中央に表示するグラフを表示

する。また、最後のデッドラインミスであるにもかかわらず、Next が呼び出された場合

には図 3.19 のようなエラーメッセージを表示する。

(37)

Listing 3.17:

デッドラインミスの後方への検索

... p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ... p u b l i c v o i d n e x t ( S t a g e s t a g e ) t h r o w s E x c e p t i o n { if ( c h a r t == n u l l ) { t h r o w new E x c e p t i o n () ; } int c u r r e n t I n d e x = 0; A r r a y L i s t < Integer > d e a d l i n e M i s s T i m e L i s t = g e t D e a d l i n e M i s s T i m e L i s t () ; if ( d e a d l i n e M i s s T i m e L i s t == n u l l || d e a d l i n e M i s s T i m e L i s t . i s E m p t y () == t r u e || d e a d l i n e M i s s T i m e L i s t . s i z e () == 0) { A l e r t n o D e a d l i n e M i s s = new A l e r t ( A l e r t T y p e . I N F O R M A T I O N ) ; n o D e a d l i n e M i s s . s e t T i t l e (" I n f o r m a t i o n ") ; n o D e a d l i n e M i s s . s e t C o n t e n t T e x t (" T h e r e is no d e a d l i n e m i s s .") ; n o D e a d l i n e M i s s . s h o w A n d W a i t () . i f P r e s e n t ( r e s p o n s e - > { if ( r e s p o n s e == B u t t o n T y p e . OK ) { r e t u r n ; } }) ; } e l s e { if ( d e a d l i n e m i s s I n d e x < d e a d l i n e M i s s T i m e L i s t . s i z e () - 1) { d e a d l i n e m i s s I n d e x ++; int l a s t T i m e = t a s k L i s t . get ( t a s k L i s t . s i z e () - 1) . g e t T i m e () ; int d e a d l i n e M i s s T i m e = d e a d l i n e M i s s T i m e L i s t . get ( d e a d l i n e m i s s I n d e x ) ; if ( l a s t T i m e <= 50 || d e a d l i n e M i s s T i m e <= 25) { c r e a t e C h a r t (0 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = 0; c u r r e n t M a x T i m e = l a s t T i m e ; } e l s e if ( l a s t T i m e >= 50 && d e a d l i n e M i s s T i m e >= l a s t T i m e - 25) { c r e a t e C h a r t ( l a s t T i m e - 50 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = l a s t T i m e - 50; c u r r e n t M a x T i m e = l a s t T i m e ; } e l s e { c r e a t e C h a r t ( d e a d l i n e M i s s T i m e - 25 , d e a d l i n e M i s s T i m e + 25 , s t a g e ) ; c u r r e n t M i n T i m e = d e a d l i n e M i s s T i m e - 25; c u r r e n t M a x T i m e = d e a d l i n e M i s s T i m e + 25; } } e l s e { int l a s t T i m e = t a s k L i s t . get ( t a s k L i s t . s i z e () - 1) . g e t T i m e () ; int d e a d l i n e M i s s T i m e = d e a d l i n e M i s s T i m e L i s t . get ( d e a d l i n e m i s s I n d e x ) ; if ( l a s t T i m e <= 50 || d e a d l i n e M i s s T i m e <= 25) { c r e a t e C h a r t (0 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = 0;

(38)

c u r r e n t M a x T i m e = l a s t T i m e ; } e l s e if ( l a s t T i m e >= 50 && d e a d l i n e M i s s T i m e >= l a s t T i m e - 25) { c r e a t e C h a r t ( l a s t T i m e - 50 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = l a s t T i m e - 50; c u r r e n t M a x T i m e = l a s t T i m e ; } e l s e { c r e a t e C h a r t ( d e a d l i n e M i s s T i m e - 25 , d e a d l i n e M i s s T i m e + 25 , s t a g e ) ; c u r r e n t M i n T i m e = d e a d l i n e M i s s T i m e - 25; c u r r e n t M a x T i m e = d e a d l i n e M i s s T i m e + 25; } A l e r t n o N e x t D e a d l i n e M i s s = new A l e r t ( A l e r t T y p e . I N F O R M A T I O N ) ; n o N e x t D e a d l i n e M i s s . s e t T i t l e (" I n f o r m a t i o n ") ; n o N e x t D e a d l i n e M i s s . s e t C o n t e n t T e x t (" T h i s is the l a s t d e a d l i n e m i s s .") ; n o N e x t D e a d l i n e M i s s . s h o w A n d W a i t () . i f P r e s e n t ( r e s p o n s e - > { if ( r e s p o n s e == B u t t o n T y p e . OK ) { r e t u r n ; } }) ; } } } ...

メニューから Deadline Miss → Previous を選択した際に呼び出される ChartCreater クラ

スの prev() メソッドの実装は以下 Listing3.18 である。また、最初のデッドラインミスで

あるにもかかわらず、Previous が呼び出された場合には図 3.20 のようなエラーメッセー

ジを表示する。

(39)

Listing 3.18:

デッドラインミスの前方への検索

... p u b l i c c l a s s C h a r t C r e a t e r e x t e n d s A p p l i c a t i o n { ... p u b l i c v o i d p r e v ( S t a g e s t a g e ) t h r o w s E x c e p t i o n { if ( c h a r t == n u l l ) { t h r o w new E x c e p t i o n () ; } A r r a y L i s t < Integer > d e a d l i n e M i s s T i m e L i s t = g e t D e a d l i n e M i s s T i m e L i s t () ; if ( d e a d l i n e M i s s T i m e L i s t == n u l l || d e a d l i n e M i s s T i m e L i s t . i s E m p t y () == t r u e || d e a d l i n e M i s s T i m e L i s t . s i z e () == 0) { A l e r t n o D e a d l i n e M i s s = new A l e r t ( A l e r t T y p e . I N F O R M A T I O N ) ; n o D e a d l i n e M i s s . s e t T i t l e (" I n f o r m a t i o n ") ; n o D e a d l i n e M i s s . s e t C o n t e n t T e x t (" T h e r e is no d e a d l i n e m i s s .") ; n o D e a d l i n e M i s s . s h o w A n d W a i t () . i f P r e s e n t ( r e s p o n s e - > { if ( r e s p o n s e == B u t t o n T y p e . OK ) { r e t u r n ; } }) ; } e l s e { if ( d e a d l i n e m i s s I n d e x > 0) { d e a d l i n e m i s s I n d e x - -; int l a s t T i m e = t a s k L i s t . get ( t a s k L i s t . s i z e () - 1) . g e t T i m e () ; int d e a d l i n e M i s s T i m e = d e a d l i n e M i s s T i m e L i s t . get ( d e a d l i n e m i s s I n d e x ) ; if ( l a s t T i m e <= 50 || d e a d l i n e M i s s T i m e <= 25) { c r e a t e C h a r t (0 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = 0; c u r r e n t M a x T i m e = l a s t T i m e ; } e l s e if ( l a s t T i m e >= 50 && d e a d l i n e M i s s T i m e >= l a s t T i m e - 25) { c r e a t e C h a r t ( l a s t T i m e - 50 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = l a s t T i m e - 50; c u r r e n t M a x T i m e = l a s t T i m e ; } e l s e { c r e a t e C h a r t ( d e a d l i n e M i s s T i m e - 25 , d e a d l i n e M i s s T i m e + 25 , s t a g e ) ; c u r r e n t M i n T i m e = d e a d l i n e M i s s T i m e - 25; c u r r e n t M a x T i m e = d e a d l i n e M i s s T i m e + 25; } } e l s e { d e a d l i n e m i s s I n d e x = 0; int l a s t T i m e = t a s k L i s t . get ( t a s k L i s t . s i z e () - 1) . g e t T i m e () ; int d e a d l i n e M i s s T i m e = d e a d l i n e M i s s T i m e L i s t . get (0) ; if ( l a s t T i m e <= 50 || d e a d l i n e M i s s T i m e <= 25) { c r e a t e C h a r t (0 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = 0; c u r r e n t M a x T i m e = l a s t T i m e ;

(40)

} e l s e if ( l a s t T i m e >= 50 && d e a d l i n e M i s s T i m e >= l a s t T i m e - 25) { c r e a t e C h a r t ( l a s t T i m e - 50 , l a s t T i m e , s t a g e ) ; c u r r e n t M i n T i m e = l a s t T i m e - 50; c u r r e n t M a x T i m e = l a s t T i m e ; } e l s e { c r e a t e C h a r t ( d e a d l i n e M i s s T i m e - 25 , d e a d l i n e M i s s T i m e + 25 , s t a g e ) ; c u r r e n t M i n T i m e = d e a d l i n e M i s s T i m e - 25; c u r r e n t M a x T i m e = d e a d l i n e M i s s T i m e + 25; } A l e r t n o N e x t D e a d l i n e M i s s = new A l e r t ( A l e r t T y p e . I N F O R M A T I O N ) ; n o N e x t D e a d l i n e M i s s . s e t T i t l e (" I n f o r m a t i o n ") ; n o N e x t D e a d l i n e M i s s . s e t C o n t e n t T e x t (" T h i s is the f i r s t d e a d l i n e m i s s .") ; n o N e x t D e a d l i n e M i s s . s h o w A n d W a i t () . i f P r e s e n t ( r e s p o n s e - > { if ( r e s p o n s e == B u t t o n T y p e . OK ) { r e t u r n ; } }) ; } } }

なお、前方への検索でも後方への検索でもデッドラインミスが存在しない場合は図 3.21

のように表示される。

図 3.21: デッドラインミスが存在しないことを示すメッセージ表示

3.8

開発時に発生した問題点と解決法

本節では開発時に発生した問題点とその解決法について記述する。本節で開発した GUI

ツールを実行し、検証を行った際に以下の 2 つの問題点が発生した。

(41)

1.

仮想マシン上で非常に長いティック範囲のグラフを表示し、ズームを繰り返すと表

示が崩れる

2.

非常に長いティック範囲のグラフを画像としてエクスポートすると、例外が発生し

てエクスポートできない

Mac OS 10

上で VMWare を使用して Windows 7 の仮想環境を構築して検証を行っていた

ところ、非常に長いティック範囲のグラフを表示し、ズームを繰り返すと表示が崩れる問

題が発生した。その際には Listing3.19 のように GUI ツールを実行していた。

Listing 3.19:

問題発生時の GUI ツールの実行方法

ja v a t a s k s c h e d u l i n g . C h a r t C r e a t e r

図 3.22 が 9997 ティック分のイベントをグラフ表示した直後の状態、図 3.23 がその後 8

回ズームを行った状態である。

図 3.22: 仮想マシン上で 9997 ティックを表示

図 3.23: 仮想マシン上で 9997 ティックを表示

した後 8 回ズームを行ったグラフ

JavaFX

ではパフォーマンス上の観点から可能な場合はハードウェア側で画像のレンダ

リングを行う動作となっている [11] が、JavaFX は正式に仮想環境をサポートしておらず、

仮想環境ではハードウェアでの画像レンダリングに問題が発生してしまうため、このよう

に表示が崩れる状態になっていた。このため、Listing3.20 のように JVM 引数 prism.order

を使用してソフトウェア・レンダリングを強制的に実行させるような設定を行い、問題を

解決することができた。

Listing 3.20:

問題解決後の GUI ツールの実行方法

ja v a - D p r i s m . o r d e r = sw t a s k s c h e d u l i n g . C h a r t C r e a t e r

(42)

また、非常に長いティック範囲のグラフを画像としてエクスポートすると、例外が発生

してエクスポートできない問題も発生していたが、同様に Listing3.20 で画像をソフトウェ

ア・レンダリングで生成させることにより解決することができた。この問題は非仮想環境

でも発生しており、9997 ティックのグラフを全範囲で表示し、その後 15 回ズーム (図 3.24

参照) してから画像エクスポートを行うと、Listing3.21 の例外が発生した。ズーム回数を

減らした場合には発生しなかった。

図 3.24: 非仮想環境で 9997 ティック表示後 15 回ズームした画面

Listing 3.21:

非仮想環境で 9997 ティック表示後 15 回ズームしたした後画像エクスポート

した際に発生した例外

ja v a . l a n g . R u n t i m e E x c e p t i o n : R e q u e s t e d t e x t u r e d i m e n s i o n s ( 1 7 7 6 4 x 4 8 0 ) r e q u i r e d i m e n s i o n s (0 x 4 8 0 ) t h a t e x c e e d m a x i m u m t e x t u r e s i z e ( 1 6 3 8 4 )

at com . sun . p r i s m . es2 . E S 2 R T T e x t u r e . c r e a t e ( E S 2 R T T e x t u r e . j a v a : 2 2 0 ) at com . sun . p r i s m . es2 . E S 2 R e s o u r c e F a c t o r y . c r e a t e R T T e x t u r e (

E S 2 R e s o u r c e F a c t o r y . j a v a : 1 5 7 )

at com . sun . p r i s m . es2 . E S 2 R e s o u r c e F a c t o r y . c r e a t e R T T e x t u r e ( E S 2 R e s o u r c e F a c t o r y . j a v a : 1 5 3 )

at com . sun . j a v a f x . tk . q u a n t u m . Q u a n t u m T o o l k i t $ Q u a n t u m I m a g e . g e t R T ( Q u a n t u m T o o l k i t . j a v a : 1 2 8 4 )

at com . sun . j a v a f x . tk . q u a n t u m . Q u a n t u m T o o l k i t $ 5 . run ( Q u a n t u m T o o l k i t . j a v a : 1 4 2 1 )

at j a v a . u t i l . c o n c u r r e n t . E x e c u t o r s $ R u n n a b l e A d a p t e r . c a l l ( E x e c u t o r s . j a v a : 5 1 1 )

at j a v a . u t i l . c o n c u r r e n t . F u t u r e T a s k . r u n A n d R e s e t ( F u t u r e T a s k . j a v a : 3 0 8 )

at com . sun . j a v a f x . tk . R e n d e r J o b . run ( R e n d e r J o b . j a v a : 5 8 ) at j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r . r u n W o r k e r (

図 2.4: Cent OS 7.2 上で本 GUI ツールを稼動させた画面ショット
図 3.2: メニューから入力ファイルの選択
図 3.6: 前 50 件表示のメニュー選択
図 3.8: 表示するティックの範囲指定
+7

参照

関連したドキュメント

Bluetooth® Low Energy プロトコルスタック GUI ツールは、Microsoft Visual Studio 2012 でビルドされた C++アプリケーションです。GUI

Löffler, 2003, Evaluating the Quality of Public Governance: Indicators, Models and Methodologies, Administration Review, Vol.. Proposta e materiali di

地震による自動停止等 福島第一原発の原子炉においては、地震発生時点で、1 号機から 3 号機まで は稼働中であり、4 号機から

意向調査実施世帯 233 世帯 訪問拒否世帯 158/233 世帯 訪問受け入れ世帯 75/233 世帯 アンケート回答世帯 50/233 世帯 有効回答数 125/233

The parameters set in trapezoidal operation can be used to start tuning sinusoidal mode. Begin with 6 window sinusoidal mode and then try to reduce the window

平成 21 年東京都告示第 1234 号別記第8号様式 検証結果報告書 A号様式 検証結果の詳細報告書(モニタリング計画).. B号様式

約3倍の数値となっていた。),平成 23 年 5 月 18 日が 4.47~5.00 (入域の目 的は同月

(ア) 上記(50)(ア)の意見に対し、 UNID からの意見の表明において、 Super Fine Powder は、. 一般の