命名って難しい

変数、関数、クラスなどなど実装より命名に毎回悩むタイプの人間による技術や趣味についてのメモ。

Wi-Fi Arubaのshow clientsを取得してDBに入れて見える化するまで

無線アクセスポイントが場所により遅いなどトラブルがあり、手作業でログを取得して解析しているチームがあったので、以下の点で手伝ってみました。

  • 自動取得からDB取り込み
  • 見える化(Power BI)

目次

目的

アクセスポイントへのアクセスを見える化して、障害時や問合せ対応のときに即座に参照できるようにする。

やったこと

自動取得からDB取り込み

データの取得

arubaにtelnetで接続し、 show clients というコマンドを実行することでデータが取れるもよう。

www.arubanetworks.com

結果はこうなる

f:id:NotShown:20190815142202p:plain
show clients 結果

データの出力

どうにかCSVにしたかったけれど方法が見つからず、しょうがないのでtelnetのログファイル出力をし、それをどうにかして読み込むことにしました。

f:id:NotShown:20190815142458p:plain
telnet /?

無事、ログファイルはできましたが、コマンドプロンプト上の改行も加味されて出力されてしまうので、コマンドプロンプトの設定を以下のように設定

  • フォント:8
  • ウインドウのサイズ: 幅250 ※環境依存かと思います。

出力データの整形

PowerShellで無理やり組みました。以上。

SignalやSpeedについては 速度と評価が併記されているので、分解して数値として取り込みやすくしました。 #現在では活用できず。。。

<#
 Aruba のログ解析(クライアント)
 telnetから標準出力をテキストとして出力したものを元にCSVとして吐き出す。
#>


# 設定値
$csvPath = "show_clients.csv"
$csvText = ""
$hasHeader = $true
$headers = "Name","IP Address","MAC Address","OS","ESSID","Access Point","Channel","Type","Role","IPv6 Address","Signal","Speed (mbps)"
$TableTitle = "Client List"
$TableTail = "Number of Clients"

# 事前処理
Remove-Item $csvPath -Force -ErrorAction Ignore
$now = (Get-Date)
$text = Get-Content .\show clients.log
$columnIndex = @()

$isSkip = $true

for($i=0; $i -lt $text.Count;$i++){
    $line = $text[$i]
    
    # ヘッダーの取得
    if($line -eq $TableTitle){
        $i += 2;
        $headerText =  $text[$i]
        # ヘッダーテーブルのフォーマット取得
        for($j = 0; $j -lt $headers.Count;$j++){
            $columnIndex += $headerText.IndexOf($headers[$j])
        }
        if($hasHeader){
            $csvText += (("DateTime,"+($headers -join ",")) + "`r`n")
        }
        $i++
        $isSkip = $false
        continue;
    }
    if($text[$i].StartsWith($TableTail)){ $isSkip = $true }

    # スキップ
    if($isSkip){ continue; }

    $csvLine = $line
    for($ci = $columnIndex.Length -1; $ci -gt 0; $ci--){
        $csvLine = $csvLine.Insert($columnIndex[$ci],",")
    }
    $csvText += (("$($now.ToString('yyyy-MM-dd HH:mm:ss.fff')),"+$csvLine) + "`r`n")
}

# Signal とSpeed を分解してわかりやすくする

ConvertFrom-Csv $csvText | 
    select DateTime,
    "Name","IP Address","MAC Address","OS","ESSID","Access Point","Channel","Type","Role","IPv6 Address",
    @{Name="Signal Value";Expression={$_.Signal -replace "\(.*","" }},
    @{Name="Signal Grade";Expression={$_.Signal -replace "[\d\(\)]",""}},
    @{Name="Speed (mbps) Value";Expression={$_."Speed (mbps)" -replace "\(.*","" }},
    @{Name="Speed (mbps) Grade";Expression={$_."Speed (mbps)" -replace  "[\d\(\)]",""}} |
    ConvertTo-Csv -NoTypeInformation |
    %{ $_.Replace('"','') -replace " +,",',' } | 
    Set-Content -Path $csvPath

データ取得処理の自動化

単純なのでVBScriptでオートメーション化しました。

command="show clients"
path="C:\Aruba\"& command &".log"
ip=""
userId=""
password =""

set oShell = CreateObject("WScript.Shell")
oShell.run "Telnet -f " & path
WScript.Sleep 1000
oShell.SendKeys("Open " & ip & "{Enter}")
WScript.Sleep 5000
oShell.SendKeys(userId & "{ENTER}")
WScript.Sleep 1000
oShell.SendKeys( password & "{ENTER}")
WScript.Sleep 1000
oShell.SendKeys(command & "{ENTER}")
WScript.Sleep 10000
oShell.SendKeys("exit{ENTER}")
WScript.Sleep 500
oShell.SendKeys("{Enter}")
WScript.Sleep 1000
oShell.SendKeys("quit{Enter}")
WScript.Sleep 1000

データの取り込み

bcp コマンドで取り込み

docs.microsoft.com

見える化(Power BI)

アクセス数

情報取得した時間のアクセス数はMACアドレスのユニークカウントとしました。 また、下記の観点でアクセス数を絞り込みます。 - アクセスポイント名 - SSID

フロアマップで見たくない?

BI製品の宣伝によくある「地図上に円がプロットされて、円のサイズでその数字を表す!」なグラフを作りたい、と思ったので散布図を活用して試してみました。

散布図に必要なデータは以下のようにしています。

  • 日時:取得日時
  • X座標:フロアマップのX座標
  • Y座標:フロアマップのY座標
  • 値:アクセスしているクライアント数

座標のとり方

Excelにフロアマップ画像を貼り付けて、背景色を透過することで力技で座標を設定。 アクセスポイント名をリレーションのキーとして紐付けます。

アクセスポイント名 X座標 Y座標
AP_FUGA 2 9
AP_HOGE 8 7
AP_FOO 7 2

出来上がり

f:id:NotShown:20190815164329p:plain
PowerBI フロアマップ散布図

中心はずれているけれど大体あえばいい。 とりあえず、なんとなく形になったので完成。

大体合計1.5日くらいかかりましたが、満足の出来です。 取り込み処理も定期実行に載せたのでこれからデータがじわじわ貯まるのが楽しみです。 これで集中しているAPなどが一目で分かり、次のアクションにつながるとよいなと思っています。

弊社現場レベルで使えるであろう簡単な社内FAQページを作った

経緯

新しい施策を社内で行うと問い合わせが増える。 その問い合わせがある程度蓄積してきたのでFAQを立てようと思い作りました。 あくまで弊社現場レベルなのでレベルは高くありません。

環境

  • Windows Server + IIS
  • 社内イントラのサーバーに間借りして設置
  • 社内イントラ関連ファイル(html/css/jsなど)のフォルダは共有されており、許可された者のみ編集可能
  • なんでもExcelで管理しているからメンテはExcelでできるとよい。
    • 弊社現場レベルで使えるのはExcelくらい

作ったもの

  1. FAQページ(Bootstrap4 + jQuery 3.3 + Vue.js)
  2. ExcelのFAQ台帳(CSVを吐くマクロ付き)
  3. PowerShellスクリプト(CSVjson形式に変換)

まずはFAQページを作り、Vue.jsでうまいことデータを表示し、
メンテに気を使いつつ、Vue.jsにわたすjsonを作れるExcelマクロとPowerShellを組みました。

全体像

こんな感じのExcelにFAQを追加して更新すると。。。 f:id:NotShown:20180820221646p:plain

FAQページがこうなります。 f:id:NotShown:20180820222047p:plain

こんな環境を作りました。

ExcelのFAQ台帳

  • FAQの源流
  • 保存前タイミングでCSVを出力、そのCSVjsonに変換するPowerShell起動
    • マクロボタンとかを用意するとメンテする人が押し忘れる可能性があるので保存前
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)

    Dim sheet As Worksheet
    Dim name As String

    Dim csv As String: csv = ThisWorkbook.Path & "\qna.csv"
    Dim ps As String: ps = ThisWorkbook.Path & "\convertQnACSV2JSON.ps1"
    
    
    Set sheet = Worksheets("Q&A")
        
        ' ファイル名
        name = ThisWorkbook.Path & "\qna.csv"

        ' シートを別のワークブックにコピーする
        sheet.Copy

        ' コピーしたワークブックを上書き保存
        Application.DisplayAlerts = False
        ActiveWorkbook.SaveAs Filename:=name, FileFormat:=xlCSV
        Application.DisplayAlerts = True

        ' コピーしたワークブックを閉じる
        ActiveWorkbook.Close SaveChanges:=False

    strCommand = "Powershell -File """ & ps & """"
    Set WshShell = CreateObject("WScript.Shell")
    WshShell.Exec (strCommand)
End Sub

PowerShellスクリプト

Excelブックマクロから出力されたCSVjson形式に変換します。

FAQデータは以下の構造として捉えています(わかりにくい)

  • ジャンル別Q&Aのリスト
    • 単一Q&A
      • Question
      • Answer
      • 参考リンクのリスト
        • タイトル
        • URL
<#
 # Q&AコンテンツのCSVをJson形式に変換する
 
 CSVレイアウトは以下
 | genre | id | q | a | title1 | url1 | title2 | url2 | ~ | url5 |
 
 #> 
Push-Location -Path (Split-Path $MyInvocation.MyCommand.Path)

$csv = Import-Csv ".\qna.csv" -Encoding Default

# スコープはjson全体
$json = @{content=(New-Object System.Collections.ArrayList)}

# スコープはジャンル別のグループ
foreach($gen in ($csv | group "genre")){

    # スコープは各Q&A
    $jgen=@{genre=$gen.Name;qnas=(New-Object System.Collections.ArrayList)}
    foreach($qna in $gen.Group){

        # Q&A参考リンクは列でタイトルとURLを表現している。
        # 5つまでのタイトルとURLの組み合わせを配列に変換する。
        $tmpLinks = @(
            @{title=$qna.title1; url=$qna.url1},
            @{title=$qna.title2; url=$qna.url2},
            @{title=$qna.title3; url=$qna.url3},
            @{title=$qna.title4; url=$qna.url4},
            @{title=$qna.title5; url=$qna.url5}
            )

        # リンクは自由入力欄のため、ある場合のみリストに加える
        $jqna = @{id=$qna.id;q=$qna.q;a=$qna.a;keywords=$qna.keywords;links=(New-Object System.Collections.ArrayList)}
        foreach($link in $tmpLinks){
            if($link.url -ne ""){
                # タイトルなしの場合URLにする
                if($link.title -eq ""){
                    $link.title = $link.url
                }
                $jqna.links.Add($link)
            }
        }
        $jgen.qnas.Add($jqna)
    }
    $json.content.Add($jgen)
}

# 出力する
Set-Content qna.json ( ConvertTo-Json $json -Depth 6 ) -Encoding UTF8

FAQページ

見た目は全体像の通り、FAQはアコーディオンで開きます。

なお、こちらはBootstrapのフリーのFAQテンプレートを流用して作っており、参考URLと検索欄の他はほぼそのまま。 www.prepbootstrap.com

このページに変換機能もあったので気まぐれでBootstrap4にアップデートしています。

テンプレからの変更点1:検索機能

検索欄に入力すると、FAQが絞れます。検索というよりは絞り込みですね。 既存の環境はいじれないのでバックエンドは考えずフロントでどうにかしています。

function search() {
    // 検索条件を取得、スペースで区切られた各条件で検索する。
    var input = document.querySelector("#search-criteria");
    var conditions = input.value.split(/ | /).filter(function(elem) {
        return elem != "";
    });

    // 正規表現の作成
    var tmpExp = "";
    conditions.forEach(function(c, i, a) {
        tmpExp += ("(?=.*" + c + ")");
    });
    var exp = "^" + tmpExp;
    var regexp = new RegExp(exp, "i");

    // Q&Aの各要素を取得。条件にヒットしないものを非表示にする。
    var divTmp = document.querySelectorAll(".qna")
    divs = Array.prototype.slice.call(divTmp, 0)
    divs.forEach(function(div, index, ar) {
        div.style.display = regexp.test(div.innerHTML) ? "" : "none";
    });
}

テンプレからの変更点2:Vue.js

以下の理由からVue.jsに触れることにしました。

  • 最近流行っている技術に触れたい
  • メンテナンスを楽にしたい
    • jsonを更新するだけで反映される構造にしたい
  • とはいえシンプルなものでさっさと作りたい
    • Reactも気になっていたんですが、環境作るのが難しそうで今回は断念

Web開発の業務経験がないので、こんな理由で使っていいのかアレですが、いろいろ興味のあるものは触れてみると楽しいので。

活用したのは「FAQのjsonデータを元にDOMを作る」目的です(言葉の使い方が正しいか不安) 以前にも riotjsを使ったことがあったので、スムーズに取り入れることができました。

具体的には以下のようなコードを書きました。

<div class="container">

    <br />
    <div class="form-group">
        <label for="usr">検索</label>
        <input type="text" class="form-control" id="search-criteria" placeholder="検索" onkeyup="search()">
    </div>
    <div class="" id="accordion">
        <div id="qnaAll">
            <div v-for="qnaByGenre in content">
                <div class="faqHeader">{{ qnaByGenre.genre }}</div>
                <div class="card qna" v-for="qna in qnaByGenre.qnas">
                    <div class="card-header">
                        <h4 class=""><a class="accordion-toggle collapsed" data-toggle="collapse" data-parent="#accordion" v-bind:href="'#' + qna.id">{{ qna.q }}</a></h4>
                    </div>
                    <div v-bind:id="qna.id" class="panel-collapse collapse">
                        <div class="card-block">
                            <div class="answer">{{ qna.a }}</div>
                            <div style="display:none">検索用キーワード {{ qna.keywords }}</div>
                            <div class="links" v-if="qna.links.length > 0">
                                <hr />
                                <h5>参考リンク</h5>
                                <ul v-for="link in qna.links">
                                    <li><a v-bind:href="link.url" target="_blank"> {{ link.title }}</a></li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

SQL Serverで全DB全テーブル全カラムのメタデータを取得する

背景

テーブルレイアウトも仕様書も、弊社内製既存のアプリがことごとく情報がなく、とりあえずDBのメタデータを出力することに。とりあえずメモ。

これらを使って開発側で仕様や意味合いなどまとめていきたい。。。

環境

コード

DECLARE @DBName NVARCHAR(256)
DECLARE @varSQL NVARCHAR(512)
DECLARE @getDBName CURSOR
SET @getDBName = CURSOR FOR
SELECT name
FROM sys.databases
CREATE TABLE #TmpTable (
    DBName NVARCHAR(256),SchemaName NVARCHAR(256),TableName NVARCHAR(256),ColumnName NVARCHAR(256), 
    ValueType NVARCHAR(256),max_length NVARCHAR(256),precision NVARCHAR(256),scale NVARCHAR(256),is_nullable NVARCHAR(256)
            
)
OPEN @getDBName
FETCH NEXT
FROM @getDBName INTO @DBName
WHILE @@FETCH_STATUS = 0
BEGIN
    print @DBName;
    SET @varSQL = 'USE ' + @DBName + ';
  INSERT INTO #TmpTable
  SELECT
   ''' + @DBName + ''' AS DatabaseName
   , SCHEMA_NAME(obj.schema_id) AS SchemaName
   , obj.name AS TableName
   , col.name AS ColumnName
   , typ.name AS ValueType
   , col.max_length 
   , col.precision 
   , col.scale 
   , col.is_nullable 
  FROM
   sys.objects obj
   INNER JOIN sys.columns col
    ON obj.object_id = col.object_id
   INNER JOIN sys.types typ
    ON col.user_type_id = typ.user_type_id
  WHERE
   obj.type = ''U''
  '
    EXEC (@varSQL)
FETCH NEXT
FROM @getDBName INTO @DBName
END
CLOSE @getDBName
DEALLOCATE @getDBName
SELECT *
FROM #TmpTable ORDER BY DBName,TableName,ColumnName
DROP TABLE #TmpTable

参考

各データベースへのループ処理

https://blog.sqlauthority.com/2008/04/29/sql-server-find-table-in-every-database-of-sql-server/

メタデータ関連

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

新春MacBook(Unibody 2010)をSSDに、RAM8GBに換装した

家族が端末ほしいと言うので新しく古いMacBook Unibody 2010mid を使えるようにHDDをSSD(500GB)に変え、RAMを8GB(4GBx2)にしてみました。

用途は簡単なプログラミングやネットサーフィンくらいかな。 前提として、元あったデータも何も捨てるのでバックアップなどは一切しません。

やったこと

  • 下調べ
  • パーツ購入
  • 換装作業
  • OS再インストール

下調べ

古いMacBookSSD化、またRAMの換装はしたことがなかったので下調べ。

分解・換装方法

写真はifixit、動画はGeekanoids氏で確認しました。

HDD

jp.ifixit.com

www.youtube.com

RAM

jp.ifixit.com

www.youtube.com

使用パーツ

新年にコンビニ受け取りし、かつ全部まとめてもらいたかったのでAmazonで注文。

HDD

秋葉館のページを参照し対応商品を確認。 www.akibakan.com

近いものをAmazonでレビューを確認しつつ購入。 www.amazon.co.jp

RAM

Apple の公式より、RAMを確認。 MacBook (13-inch, Mid 2010) - 技術仕様

Amazonからこれを購入。 www.amazon.co.jp

精密ドライバー

レビューもまずまずで、かっこよかった(キッズ並観点)のでこれを購入。

www.amazon.co.jp

換装作業

動画そのまま、問題なく実行できました。 特にメモリは Geekanoidsさんの動画で挿すときに角度をつけるなど、気をつけるべきところがわかったので助かりました。 今気づいたんですが、ifixitにあるバッテリー外し忘れてましたね。

OS再インストール

OS再インストールは

付属DVDからSnowLeopardをインストール

個人情報やAppleIDについては入力しませんでした。

ソフトウェアアップデート(トラブルあり)

SnowLeopardをインストール完了し、ソフトウェアアップデートをかけたら デフォルトの壁紙だけ表示されており、マウスポインタは動かせるが、何もすすまない状態になりました。

調べたところ、以下のQ&Aで解決しました。

SnowLeopard10.6.3でソ… - Apple コミュニティ

そうしたらしばらくアップデートと再起動の繰り返しです。

El Capitanへのアップデート

App Storeからダウンロードするはずなんですが、検索しても見つからないので AppleのサイトからApp Storeを開くリンクを使って移動し、インストールしました。

support.apple.com

以上! いい感じに使えるといいなぁ。

源泉徴収票には賞与が含まれる。

久々の記事でまったくコンテンツ性のないタイトル落ちの記事です。 本文を見ている方には申し訳がありません。 ふと思い立って検索するとタイトルで人を寄せて内容を読ませるタイプの記事が多かったのでタイトルだけで終わる記事もいいのでは?と書きました。

以上!

「画像を表示」を復活させるブックマークレットを作ってみたらもっといいものが既にあった

あらすじ

該当記事

forest.watch.impress.co.jp

私の作ったもの

2018/02/20 版

javascript:(function(){
    function convertHref2Url(href){
        var startTag = "/imgres?imgurl=";
        var endTag   = "&imgrefurl=";
        return decodeURIComponent(href.substring(href.indexOf(startTag) + startTag.length, href.indexOf(endTag)));
    };
    Array.from(document.querySelectorAll("a[jsname='hSRGPd']"),  e => {
        var imageLink = document.createElement("a");
        imageLink.setAttribute("target","_blank");
        imageLink.style ="height:30px;width:80px;" +
            "background-color:rgba(130, 130, 255, 0.8);" +
            "color:rgba(255, 255, 255, 1.0);" +
            "z-index:1;" +
            "border-radius: 10px;" +
            "display: flex;" +
            "flex-direction: column;" +
            "justify-content: center;" + 
            "align-items: center;" +
            "position: absolute;" +
            "text-decoration: none;";
        imageLink.href = convertHref2Url(e.href);
        var linkTitle = document.createElement("div");
        linkTitle.style = "color:black;font-size:5px;";
        linkTitle.innerText = "View image";
        imageLink.appendChild(linkTitle);
        e.parentNode.appendChild(imageLink);
    });
})();

2018/02/21 版

面白そうだったので色々試した

  • スクロールした後、リンクのついてない画像が出て来る。 何度も実行することを考慮して重複しないようにした。
  • すべての追加される要素にstyle属性を直書きすると文字数がすごいことになる(影響不明)のでstyle要素を追加した。 style要素も重複対応済。
javascript: (function () {
    function convertHref2Url(href) {
        var startTag = "/imgres?imgurl=";
        var endTag = "&imgrefurl=";
        return decodeURIComponent(href.substring(href.indexOf(startTag) + startTag.length, href.indexOf(endTag)));
    };

    var styleId = "gas_view-image_style";
    var labelClass = "gas_view-image_label";
    var linkClass = "gas_view-image_link";
    var s = document.querySelector("#" + styleId);
    if (s === null) {
        var style = document.createElement("style");
        style.setAttribute("id", styleId);
        style.nodeType = "text/css";
        style.appendChild(document.createTextNode(`
        .${labelClass} {
            height:30px;
            width:80px;
            background-color:rgba(130, 130, 255, 0.9);
            color:rgba(255, 255, 255, 1.0);
            z-index:1;
            border-radius: 10px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            position: absolute;
            text-decoration: none;
        }
        .${linkClass}{
            color:black;
            font-size:8px;
        }
        `));
        document.head.appendChild(style);
    }

    Array.from(document.querySelectorAll("a[jsname='hSRGPd']"), e => {
        if (e.parentNode.querySelector("." + labelClass) === null) {
            var imageLink = document.createElement("a");
            imageLink.setAttribute("target", "_blank");
            imageLink.setAttribute("class", labelClass);
            imageLink.href = convertHref2Url(e.href);

            var linkTitle = document.createElement("div");
            linkTitle.innerText = "View image";
            linkTitle.setAttribute("class", linkClass);
            imageLink.appendChild(linkTitle);
            e.parentNode.appendChild(imageLink);
        }
    });
})();

あと記事の問題ですが、コードハイライトが有効になるように修正。 (javascript を js にしていた。)

動作

こんな感じに「View image」のリンクが作られます。

f:id:NotShown:20180221002250p:plain

Google 画像検索 動作結果

javascriptどころかweb系全然触れてない(言い訳)のでコードの適当さがあります。

以上!

configを修正してもリモートホストからKibanaにアクセスできない場合

Windows ファイアーウォール で node.exe を許可しろ!!!

※ kibanaのバージョンアップごとに忘れて毎回時間食うので一言書きました。

以上。