Jenkins使ってますか
みなさんはJenkins使ってますか? 僕は初心者なので全然詳しくないですが、Jenkinsが好きです。業務でも使ってます。
Jenkinsは本体にもすばらしい機能がたくさんありますが、豊富なプラグインによってさらに機能を拡張できます。
このことによって、JenkinsはCIツールの枠を超え、開発におけるポータルサイトのような、プロジェクトの骨格をなすもののような、とても崇高な位置にたどり着いていると思うのです。
Jenkins何に使ってますか?
みなさんはJenkisをどんな用途で使っていますか? 単にビルドの成功失敗を見るためだけに使っているなんて、もったいない!
僕はJenkinsを見れば、明日の天気から星座占い、ニュースからJOJOの名言まですべて見れるようにしたいのです(僕はJOJOに詳しくありませんが、別にシャアの名言でもいいのです)!
なんでもJenkinsでやってしまう!
JenkinsとEmacsがあれば、すべてができてしまう! そんな世界に憧れるのです!
そうした用途の一つとして、ここ数年夏と冬に現れる、電力需給状況があります。 Jenkinsで電力需給状況を確認して、逼迫すればJenkinsを落とす!そんな男らしい使い方をしたいのです。
Jenkinsプラグインを作る
Jenkinsはもちろんプラグインを自作することができます。
Plugin tutorial - 日本語 - Jenkins Wiki
これでHello Worldできます。 蛙本を読むと、もう少し詳しくプラグインの作り方が載ってます。
- 作者: John Ferguson Smart,Sky株式会社玉川竜司
- 出版社/メーカー: オライリージャパン
- 発売日: 2012/02/22
- メディア: 大型本
- 購入: 12人 クリック: 317回
- この商品を含むブログ (30件) を見る
しかし、ここから先に進むためには、あまりドキュメントがありません。 そこで似たようなプラグインを探します。
東京電力関連のプラグインがありました。
TEPCO Plugin https://wiki.jenkins-ci.org/display/JENKINS/TEPCO+Plugin
TEPCO Electric Power Usage Widget - Jenkins - Jenkins Wiki https://wiki.jenkins-ci.org/display/JENKINS/TEPCO+Electric+Power+Usage+Widget
筆者は関西在住なので、関西電力のプラグインを作ることにします。
関西電力プラグイン
これをそのままパクれば拝借すれば、もしやURLを書き換えるくらいで完成か…!?
と思いきや、そうはいきませんでした。
CSVのフォーマットが違うのです。
2012/12/5 21:05 UPDATE,,, ピーク時供給力(万kW),時間帯,供給力情報更新日,供給力情報更新時刻,原子力,火力,水力,揚水,地熱・太陽光,他社受電,(北海道再掲),(東北再掲),(東京再掲),(中部再掲),(北陸再掲),(中国再掲),(四国再掲),(九州再掲) 2593,17:00〜18:00,12/5,9:30,240,1402,173,363,0,416,0,0,0,0,0,0,0,0 予想最大電力(万kW),時間帯,予想最大電力情報更新日,予想最大電力情報更新時刻 2260,17:00〜18:00,12/5,9:30 使用率(%),予想気温(最低),予想気温(最高),気温実績(最低),気温実績(最高) 87,3,9,*,* DATE,TIME,当日実績(万kW),予想値(万kW),前日実績(万kW),使用率(%) 2012/12/5,0:00,1679,0,1575,64 2012/12/5,1:00,1625,0,1531,62 2012/12/5,2:00,1637,0,1554,63 2012/12/5,3:00,1616,0,1549,62 2012/12/5,4:00,1572,0,1516,60 2012/12/5,5:00,1563,0,1490,60 2012/12/5,6:00,1718,0,1606,66 2012/12/5,7:00,1870,0,1755,72 2012/12/5,8:00,2043,0,1926,78 2012/12/5,9:00,2145,0,2041,82 2012/12/5,10:00,2149,0,2043,82 2012/12/5,11:00,2155,0,2035,83 2012/12/5,12:00,2028,0,1911,78 2012/12/5,13:00,2111,0,2011,81 2012/12/5,14:00,2100,0,2015,80 2012/12/5,15:00,2086,0,2014,80 2012/12/5,16:00,2164,0,2096,83 2012/12/5,17:00,2231,0,2209,86 2012/12/5,18:00,2195,0,2195,84 2012/12/5,19:00,2127,0,2130,82 2012/12/5,20:00,0,2090,2053,80 2012/12/5,21:00,0,2020,1971,77 2012/12/5,22:00,0,1930,1898,74 2012/12/5,23:00,0,1830,1795,70 翌日のピーク時供給力(万kW),時間帯,供給力情報更新日,供給力情報更新時刻,翌日の原子力,翌日の火力,翌日の水力,翌日の揚水,翌日の地熱・太陽光,翌日の他社受電,(翌日の北海道再掲),(翌日の東北再掲),(翌日の東京再掲),(翌日の中部再掲),(翌日の北陸再掲),(翌日の中国再掲),(翌日の四国再掲),(翌日の九州再掲) 2553,17:00〜18:00,12/5,18:30,240,1402,175,334,0,403,0,0,0,0,0,0,0,0 翌日の予想最大電力(万kW),時間帯,予想最大電力情報更新日,予想最大電力情報更新時刻 2260,17:00〜18:00,12/5,18:30 翌日の使用率(%),予想気温(最低),予想気温(最高), 88,7,9, 節電コメント , DATE,TIME,瞬間値(万kW) 2012/12/5,0:00,1728 2012/12/5,0:03,1722 2012/12/5,0:06,1707 (中略) 2012/12/5,23:51, 2012/12/5,23:54, 2012/12/5,23:57,
長いですね。 1つのファイルにさまざまな形式でデータが書かれています。めんどくさいですね。
関西電力プラグインを実装してみる
とりあえず、TEPCO Pluginのマネをして、この形式のCSVを読み取るようにします。
@Extension public static class CsvDownloader extends PeriodicWork { private static final String CSV_URL = "http://www.kepco.co.jp/yamasou/juyo1_kansai.csv"; private static final Pattern HEADER_PATTERN = compile("(\\d{4}/\\d{1,2}/\\d{1,2} \\d{1,2}:\\d{1,2}) UPDATE,,,"); private static final Pattern PEAK_PATTERN = compile("(\\d+),(\\d{1,2}:\\d{1,2})〜(\\d{1,2}:\\d{1,2}),\\d{1,2}/\\d{1,2},(\\d{1,2}:\\d{1,2}),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)"); private static final Pattern FORECAST_PATTERN = compile("(\\d+),(\\d{1,2}:\\d{1,2})〜(\\d{1,2}:\\d{1,2}),\\d{1,2}/\\d{1,2},(\\d{1,2}:\\d{1,2})"); private static final Pattern HOURLY_DATA_PATTERN = compile("(\\d{4}/\\d{1,2}/\\d{1,2}),(\\d{1,2}:\\d{1,2}),(\\d+),(\\d+),(\\d+),(\\d+)"); private static final Pattern MOMENTARY_DATA_PATTERN = compile("(\\d{4}/\\d{1,2}/\\d{1,2}),(\\d{1,2}:\\d{1,2}),(\\d+)"); @Override protected void doRun() throws Exception { Date lastUpdated = null; int peakCapacity = 0; int forecastPeakUsage = 0; int forecastPeakPeriod = 0; DateFormat fmt = new SimpleDateFormat("yyyy/M/d H:m"); List<UsageCondition> usages = new ArrayList<UsageCondition>(); UsageCondition current = new UsageCondition(); for (String line : loadCsv().split("\r?\n")) { Matcher m = HEADER_PATTERN.matcher(line); if (m.matches()) { lastUpdated = fmt.parse(m.group(1)); continue; } m = PEAK_PATTERN.matcher(line); if (m.matches()) { peakCapacity = Integer.parseInt(m.group(1)); continue; } m = FORECAST_PATTERN.matcher(line); if (m.matches() && forecastPeakUsage == 0) { forecastPeakUsage = Integer.parseInt(m.group(1)); String forecastPeakTime = m.group(2); forecastPeakPeriod = Integer.parseInt(forecastPeakTime.substring(0, forecastPeakTime.indexOf(':'))); continue; } m = HOURLY_DATA_PATTERN.matcher(line); if (m.matches()) { UsageCondition u = new UsageCondition(); u.setCapacity(peakCapacity); int usage = Integer.parseInt(m.group(3)); if (0 < usage) { u.setUsage(usage); } else { u.setForecastUsage(Integer.parseInt(m.group(4))); } u.setForecastPeakUsage(forecastPeakUsage); u.setForecastPeakPeriod(forecastPeakPeriod); u.setUsageUpdated(fmt.format(lastUpdated)); Date date = fmt.parse(String.format("%s %s", m.group(1), m.group(2))); Calendar c = Calendar.getInstance(); c.setTime(date); u.setYear(c.get(Calendar.YEAR)); u.setHour(c.get(Calendar.HOUR_OF_DAY)); u.setMonth(c.get(Calendar.MONTH) + 1); u.setDay(c.get(Calendar.DATE)); u.setPercentage(Integer.parseInt(m.group(6))); usages.add(u); } m = MOMENTARY_DATA_PATTERN.matcher(line); if (m.matches()) { if (m.group(3).isEmpty()) { break; } current.setUsage(Integer.parseInt(m.group(3))); current.setUsageUpdated(m.group(1) + " " + m.group(2)); current.setCapacity(peakCapacity); current.setForecastPeakUsage(forecastPeakUsage); current.setForecastPeakPeriod(forecastPeakPeriod); } } if (lastUpdated != null) { for (Widget w : Hudson.getInstance().getWidgets()) { if (w instanceof KepcoWidget) { KepcoWidget kw = (KepcoWidget) w; kw.setCurrent(JSONSerializer.toJSON(current).toString()); kw.setToday(JSONSerializer.toJSON(usages).toString()); } } } } }
PeriodicWorkクラスを継承し、この処理がJenkinsから呼び出されるようにします。
単純に正規表現で当てて、どの内容なのか判断してます。 で、値をオブジェクトに詰めてます。 最後にJenkins(Hudson)オブジェクトからこのウィジェットを取り出し、JSON文字列をセットします。
CSVファイルをダウンロードする処理はこうです。
protected String loadCsv() { InputStream is = null; try { URL url = new URL(CSV_URL); is = ProxyConfiguration.open(url).getInputStream(); return IOUtils.toString(is, "Shift_JIS"); } catch (MalformedURLException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } finally { IOUtils.closeQuietly(is); } }
CSVファイルは文字エンコーディングがShift_JISでした。 Jenkinsにプロキシを設定しているときは、その設定を使いたいので
ProxyConfiguration.open(url).getInputStream();
としてます。
ウィジェット?
前の節で、ウィジェットという単語をいきなり出しました。 これはJenkinsの拡張ポイントです。
@Extension public class KepcoWidget extends Widget { private static final Logger logger = Logger.getLogger(KepcoWidget.class.getName()); private String current; private String today; public String getCurrent() { return current; } public void setCurrent(String current) { this.current = current; } public String getToday() { return today; } public void setToday(String today) { this.today = today; } }
Widgetクラスを継承して、@Extensionアノテーションを付与しておけばいいようです。 これで、CSVをダウンロードして、パースする部分ができました。 あとはビューを書くだけです。
ビュー
TEPCO Electric Power Usage Widgetを参考に、少々書き換えました。
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt"> <l:pane width="3" title="関西電力 電気使用状況"> <tr> <td colspan="3" align="center" style="padding: 3px 0px;"> <table cellspacing="0" cellpadding="0" width="180px"> <tr id="gauge"></tr> </table> </td> </tr> <tr> <td class="pane" align="right">使用量</td> <td class="pane" align="center"><b id="usage">0</b></td> <td class="pane" rowspan="2" align="center" valign="center" style="font-size:200%;" id="percentage"></td> </tr> <tr> <td class="pane" align="right">供給能力</td> <td class="pane" align="center"><b id="capacity">0</b></td> </tr> <tr> <td class="pane" align="center" colspan="3" id="updatedTime"></td> </tr> <tr> <td colspan="3" align="center" valign="center" style="padding: 1px;"> <table cellspacing="0" cellpadding="0" height="105px" width="200px"> <tr> <td id="graph" style="position: absolute;padding: 0px;"></td> </tr> </table> </td> </tr> <script> (中略) </script> </l:pane> </j:jelly>
JenkinsはJellyを使ってビューを書きます。そんなに特殊なことはないんで、慣れればすぐ書けます。 グラフもTEPCO Electric Power Usage Widgetのグラフを書き換えました。がんばってJavaScriptを書いてます。 あんまりにも長い上に読みづらいので、掲載しませんw
スクリーンショット
こんな感じです。
まとめ
今回のプラグインは、まったく自分で作っていないですね。TEPCO PluginとTEPCO Electric Power Usage Widgetの作者に感謝です。 このように、既存のプラグインをベースに少し付け加えれば、簡単にプラグインを作ることができます。
ソースコード
https://github.com/jyukutyo/kepco-pluginにあるので、よかったらどうぞ。