↑自分は12:00位からです
こんにちは。フォロアー泥棒の@dateofrockです。
思い起こせば、第一回でSQSネタを、第二回でSNS自爆ネタを発表してからはや一年が経ちました。その間に、QCon Tokyo 2010とか、女子会とか、札幌支部の勉強会でしゃべったりと、結構いろんなところでお話しさせていただく機会をもらい、大変ありがたく思います。(沖縄行きてー)
AndroidでTwitter follower数をCustom Metricsに突っ込む
お約束の「続きはWebで」の件ですが、まずJMXを使ったCustom Metricsはこちらをご覧ください。で、これをAndroidからやるためには、cron的な動作をさせる事が出来るAlarmManagerを使うのが便利です。
Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package com.dateofrock.android.test; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class Main extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.main); // 監視開始ボタン Button startWatchButton = (Button) findViewById(R.id.startWatchButton); startWatchButton.setOnClickListener( new View.OnClickListener() { public void onClick(View v) { long interval = 60 * 1000 ; // 1分間隔 // Intent作成 PendingIntent pendingIntent = createPendingIntent(); // AlarmManagerに登録 AlarmManager am = (AlarmManager) Main. this .getSystemService(ALARM_SERVICE); am.setRepeating(AlarmManager.ELAPSED_REALTIME, 1000 , interval, pendingIntent); } }); // 監視終了ボタン Button stopWatchButton = (Button) findViewById(R.id.stopWatchButton); stopWatchButton.setOnClickListener( new View.OnClickListener() { public void onClick(View v) { PendingIntent pendingIntent = createPendingIntent(); AlarmManager am = (AlarmManager) Main. this .getSystemService(ALARM_SERVICE); am.cancel(pendingIntent); Toast.makeText(getBaseContext(), "Twitterの監視をやめますた" , 10 ).show(); } }); } private PendingIntent createPendingIntent() { Intent intent = new Intent(getBaseContext(), TwitterWatchBroadcastReceiver. class ); PendingIntent pendingIntent = PendingIntent.getBroadcast( getBaseContext(), 0 , intent, 0 ); return pendingIntent; } } |
res/layout/Main.xml
1 2 3 4 5 6 7 8 9 10 11 | <? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "fill_parent" android:layout_height = "fill_parent" android:orientation = "horizontal" > < Button android:id = "@+id/startWatchButton" android:layout_height = "wrap_content" android:layout_width = "wrap_content" android:text = "@string/start_watch_string" ></ Button > < Button android:layout_height = "wrap_content" android:layout_width = "wrap_content" android:id = "@+id/stopWatchButton" android:text = "@string/stop_watch_string" ></ Button > </ LinearLayout > |
極限まで研ぎすまされた無駄のないユーザーインターフェース
Main.javaは、アプリケーションを起動した時に最初に表示されるActivityです。スタートボタンが押されるとAlarmManager#setRepeating()するのですが、ここで渡されるPendingIntentはBroadcastReceiverを継承したTwitterWatchBroadcastReceiverを起動させるものになります。この中で、Twitter APIに現在のフォロワー数を聞きに行って、それをCloudWatchにputMetricDataするだけです。
上記のコードでは1分間隔で監視していますが、これは結構電池を消耗するのでおススメしません。今回はどれだけ短時間でフォロワーが増えるかみたかったので、あえて1分にしています。
TwitterWatchBroadcastReceiver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.dateofrock.android.test; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.text.format.DateUtils; import com.dateofrock.android.test.TwitterClient.APIResult; public class TwitterWatchBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Twitterにお伺い TwitterClient twitter = new TwitterClient(); APIResult result = twitter.getResult( "dateofrock" ); // 結果をCloudWatchに送信 CloudWatchClient cloudWatch = new CloudWatchClient(); cloudWatch.sendMetrics( "dateofrock" , result); // Notify doNotification(context); } private void doNotification(Context context) { NotificationManager notificationManager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); String text = "CloudWatchにTwitter情報を登録しました(" + DateUtils.formatDateTime(context, System.currentTimeMillis(), DateUtils.FORMAT_SHOW_TIME) + ")" ; Notification notification = new Notification( android.R.drawable.btn_star, text, System.currentTimeMillis()); notification.flags = Notification.FLAG_AUTO_CANCEL; Intent intent = new Intent(context, Main. class ); PendingIntent contentIntent = PendingIntent.getActivity( context.getApplicationContext(), 0 , intent, 0 ); notification.setLatestEventInfo(context, "TwitterWatch" , text, contentIntent); notificationManager.notify(R.string.app_name, notification); } } |
CloudWatchClient.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package com.dateofrock.android.test; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient; import com.amazonaws.services.cloudwatch.model.Dimension; import com.amazonaws.services.cloudwatch.model.MetricDatum; import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest; import com.amazonaws.services.cloudwatch.model.StandardUnit; import com.dateofrock.android.test.TwitterClient.APIResult; public class CloudWatchClient { public void sendMetrics(String twitterUser, APIResult result) { if (result.isError()) { return ; } Dimension dimension = new Dimension().withValue(twitterUser).withName( "User" ); // リクエストの組み立て PutMetricDataRequest request = new PutMetricDataRequest() .withNamespace( "Twitter" ).withMetricData( new MetricDatum().withMetricName( "Follower" ) .withDimensions(dimension) .withUnit(StandardUnit.Count.toString()) .withValue(( double ) result.followersCount), new MetricDatum().withDimensions(dimension) .withMetricName( "Friends" ) .withUnit(StandardUnit.Count.toString()) .withValue(( double ) result.friendsCount), new MetricDatum().withDimensions(dimension) .withMetricName( "Status" ) .withUnit(StandardUnit.Count.toString()) .withValue(( double ) result.statusCount) ); // CloudWatchにデータ送信 AWSCredentials cred = new BasicAWSCredentials( "アクセスキー" , "シークレットキー" ); AmazonCloudWatchClient client = new AmazonCloudWatchClient(cred); client.setEndpoint(CloudWatchEndPoint.US_EAST); client.putMetricData(request); } } |
CloudWatchEndPoint.java
1 2 3 4 5 6 7 8 9 10 11 | package com.dateofrock.android.test; public class CloudWatchEndPoint { public static final String US_EAST = "monitoring.us-east-1.amazonaws.com" ; public static final String US_WEST = "monitoring.us-west-1.amazonaws.com" ; public static final String EU_WEST = "monitoring.eu-west-1.amazonaws.com" ; public static final String AP_SOUTHEAST = "monitoring.ap-southeast-1.amazonaws.com" ; public static final String AP_NORTHEAST = "monitoring.ap-northeast-1.amazonaws.com" ; } |
TwitterClient.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package com.dateofrock.android.test; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.TwitterFactory; import twitter4j.User; public class TwitterClient { public APIResult getResult(String twitterUser) { APIResult result = new APIResult(); Twitter twitter = new TwitterFactory().getInstance(); User user; try { user = twitter.showUser(twitterUser); result.followersCount = user.getFollowersCount(); result.friendsCount = user.getFriendsCount(); result.statusCount = user.getStatusesCount(); } catch (TwitterException e) { result.error = true ; } finally { return result; } } public class APIResult { int followersCount, friendsCount, statusCount; private boolean error = false ; public boolean isError() { return this .error; } } } |
TwitterWatchBroadcastReceiver#onReceive()で起動されるTwitterClientですが、Twitter APIのGET users/showが幸い認証が不要なので、非常に簡素に書く事ができます。ただし、Twitterの状況によっては取得に失敗する事もありますし、ましてやケータイは圏外になる事もよくありますので、例外はキャッチして取得に失敗したという状況が分かるようにして(APIResult.error=true)値を返すようにしています。というわけで、エラー処理は適当ですw
結果発表
で、どのくらいTwitterのフォロワーが増えたかを貼っておきますとこんな感じでした。わずか3分で純増70弱です。協力していただいた皆様、本当にありがとうございます!
あれから半日以上経過しましたが、若干の増減はあるものの、今のところキープですww
ちなみに、所々グラフが切れている部分がありますが、これはTwitterAPIやCloudWatchAPIの呼び出しに失敗したからだと思います。原因はほぼ間違いなくAndroid端末の通信状況です。移動中でトンネルの中に入っていたりすると圏外になってしまい、失敗します。
ちなみに、CloudWatchではあるしきい値を超えるとAmazon SNS経由でメールを飛ばす事が出来るのですが、それもしゃべっている間に来ておりましたw
![]() |
実際に来たメール(クリックで拡大) |
![]() |
Alarmの設定画面(クリックで拡大) |
CloudWatch Custom Metricsの注意点
5分という制限時間ではお伝えしきれなかった事ををここにメモしておきます。※2番目、3番目はCustom Metricsに限った話ではありません。- 消す手段が無いw
- 時刻表記がUTCのみ
これは、Management Consoleで見た場合に非常に分かりにくいのでなんとかして欲しいところです < AWS様 - グラフデータを画像として取得できない
ただし、APIのGetMetricStatisticsを叩けば、DataPointがドバドバっと取得できますので、自力でグラフを描く事は可能です。ただ、どうせなら絵にして欲しいところです < AWS様
まとめ
今回はネタとしてわざとAndroidとかTwitterなどを出してみましたが、これはCloudWatchのCustom Metricsって使い勝手があるよ、という事を強調したかったからです。(というか、フォロワーを増やしてみたかっただけ?)通常OSやアプリケーションレベルのメトリクスをとりたい場合、Nagios、Zabbix、Muininあたりが出てくるのでしょうけれども、クラウド環境でそれらを動かすってのもある意味芸が無いというか、そもそも監視サーバのお守りもなんだかなぁ、と思うので、CloudWatchに全て統合できれば嬉しいな、という個人的見解です。
もう、ここまで来るとIaaSとかPaaSっていうレイヤー分けが意味が無い気がする今日この頃でした・・
0 件のコメント :
コメントを投稿