メディアのエンコーディング ジョブでの課金対象のデータ量を計算する方法


このポストは、7 月 16 日に投稿した Calculating Billable Gigabytes for Media Encoding Jobs の翻訳です。

Media Services では、メディア プロセッサの 1 つである Azure Media Encoder をタスクで使用し、そのタスクが含まれるジョブを送信した場合に、処理されたデータ量に応じて料金が発生します。Azure 課金ポータルでは、請求期間内に処理されたデータ量の合計をギガバイト単位で確認できますが、ジョブ単位やタスク単位の内訳はわかりません。このブログ記事では、課金対象のデータ量の内訳をジョブ単位およびタスク単位で計算するサンプル コード、およびその計算結果を Excel の Power Query で分析する方法についてご紹介します。

ストレージ内のメディア アセット

メディア アセットを作成する場合、Media Services では GUID が生成され、この GUID から、“nb:cid:UUID:” というプレフィックスの後に GUID が続くメディア アセットの ID が作成されます。つまり、メディア アセットの ID は “nb:cid:UUID:<GUID>” という形になります。また Media Services では、指定されたストレージ アカウントに “asset-<GUID>” という名前のコンテナーが作成されます。アセットの作成が完了したら、アセット ファイルをストレージ コンテナーにアップロードできます。エンコーディング タスクを含むジョブを送信した場合、エンコーダーからの出力ファイルは出力されるアセットに関連付けられたストレージ コンテナーに格納されます。

サンプル コード

ご紹介するサンプル コードは下記の処理を行います。

  • 指定された Media Services アカウントのジョブをすべてリストにまとめます。
  • 各ジョブのタスクをすべてリストにまとめます。
  • 完了済みタスクについて、入力アセットと出力アセットをすべてリストにまとめます。
  • 各入力アセットおよび出力アセットについて、それぞれのアセットに関連付けられているストレージ コンテナーの BLOB をすべてリストにまとめ、アセットの各 BLOB のサイズを合計してアセットのサイズを計算します。
  • パーティション キーとしての JobId と、行キーとしての TaskId を含む、JobAndTaskTableEntityという名前の Azure Tables エンティティを作成します。
  • JobAndTaskTableEntity には、StartTime、EndTime、MediaProcessor、RunningDuration、InputAssetSize、OutputAssetSize などの他のメンバーも含まれます。
  • さらに、JobAndTaskTableEntityJobAndTaskMetadata という名前の Azure Tables に書き出します。

 

サンプル コードの App.Config ファイルは次のようになります。

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <startup>

    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />

  </startup>

  <appSettings>

    <add key="MediaServicesAccountName" value="<MediaAccountName>" />

    <add key="MediaServicesAccountKey" value="<MediaAccountKey>" />

    <add key="StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=<StorageAccountName>;AccountKey=<StorageAccountKey>"/>

  </appSettings>

  <runtime>

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

      <dependentAssembly>

        <assemblyIdentity name="Microsoft.WindowsAzure.Storage" publicKeyToken="31bf3856ad364e35" culture="neutral" />

        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />

      </dependentAssembly>

    </assemblyBinding>

  </runtime>

</configuration>

上の App.Config 内の、<MediaAccountName> および <MediaAccountKey> の部分はお客様の Media Services アカウントの名前とキーに変更してください。また、<StorageAccountName> および <StorageAccountKey> の部分は、Media Services アカウントに関連付けられているストレージ アカウントの名前とキーに変更してください。

サンプル コードは次のとおりです。

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Microsoft.WindowsAzure;

using Microsoft.WindowsAzure.Storage;

using Microsoft.WindowsAzure.Storage.Blob;

using Microsoft.WindowsAzure.Storage.Table;

using Microsoft.WindowsAzure.MediaServices.Client;

using System.Configuration;

 

namespace JobAndTaskBilling

{

    /// <summary>

    ///

    /// </summary>

    public class JobAndTaskTableEntity : TableEntity

    {

        public DateTime StartTime { get; set; }

        public DateTime EndTime { get; set; }

        public string MediaProcessor { get; set; }

        public TimeSpan RunningDuration { get; set; }

        public Double InputAssetSize { get; set; }

        public Double OutputAssetSize { get; set; }

    }

 

    /// <summary>

    ///

    /// </summary>

    class Program

    {

        // App.config ファイルから値を読み込む

        private static readonly string _mediaServicesAccountName =

            ConfigurationManager.AppSettings["MediaServicesAccountName"];

        private static readonly string _mediaServicesAccountKey =

            ConfigurationManager.AppSettings["MediaServicesAccountKey"];

        private static readonly string _storageConnectionString =

            ConfigurationManager.AppSettings["StorageConnectionString"];

 

        //

        private static CloudStorageAccount _cloudStorage = null;

        private static CloudBlobClient _blobClient = null;

 

        private static CloudTableClient _tableClient = null;

        private static CloudTable _taskTable = null;        

 

        private static CloudMediaContext _context = null;

        private static MediaServicesCredentials _cachedCredentials = null;

 

        static void Main(string[] args)

        {

            try

            {

                // Media Services の認証情報を静的クラス変数の形で作成しキャッシュする

                _cachedCredentials = new MediaServicesCredentials(_mediaServicesAccountName, _mediaServicesAccountKey);

 

                // キャッシュされた認証情報から CloudMediaContext を作成する

                _context = new CloudMediaContext(_cachedCredentials);

 

                // App.Config に含まれているストレージの接続文字列から CloudStorageAccount インスタンスを作成する

                _cloudStorage = CloudStorageAccount.Parse(_storageConnectionString);

 

                _blobClient = _cloudStorage.CreateCloudBlobClient();   // CloudBlobClient インスタンスを作成して BLOB 操作を行う

                _tableClient = _cloudStorage.CreateCloudTableClient(); // CloudTableClient インスタンスを作成してテーブル操作を行う

 

                _taskTable = _tableClient.GetTableReference("JobAndTaskMetadata");

                _taskTable.CreateIfNotExists();  // JobAndTaskMetadata が未作成の場合は作成する

 

                ProcessJobs();

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

 

        /// <summary>

        /// この関数では、Media Services アカウントのすべてのジョブをループ処理する

        /// </summary>

        static void ProcessJobs()

        {

            try

            {

                Dictionary<string, string> _dictMPs = GetMediaProcessors();

 

                int skipSize = 0;

                int batchSize = 1000;

                int currentBatch = 0;               

 

                while (true)

                {

                // Media Services アカウントのすべてのジョブをループ処理する (1 回あたり 1,000 個)

                    IQueryable _jobsCollectionQuery = _context.Jobs.Skip(skipSize).Take(batchSize);

                    foreach (IJob job in _jobsCollectionQuery)

                    {

                        currentBatch++;

                        Console.WriteLine("Processing Job Id:" + job.Id);

 

                        ProcessTasks(job, _dictMPs);

                    }

 

                    if (currentBatch == batchSize)

                    {

                        skipSize += batchSize;

                        currentBatch = 0;

                    }

                    else

                    {

                        break;

                    }                   

                }

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

 

        /// <summary>

        /// Media Services アカウントで使用可能なすべてのメディア プロセッサのリストを作成し、キーが MediaProcessorId、値が MediaProcessorName の辞書を作成する

        /// </summary>

        /// <returns></returns>

        static Dictionary<string, string> GetMediaProcessors()

        {

            Dictionary<string, string> _dictMPs = new Dictionary<string, string>();

            foreach (IMediaProcessor mp in _context.MediaProcessors)

            {

                _dictMPs.Add(mp.Id, mp.Name);               

            }

 

            return _dictMPs;

        }

 

        /// <summary>

        /// この関数では、ジョブに関連付けられているすべてのタスクをループ処理する

        /// すべての完了済みタスクについて、入力アセットおよび出力アセットのサイズを計算し JobsAndTasksMetadata テーブルにエンティティを書き出す

        /// </summary>

        /// <param name="job"></param>

        static void ProcessTasks(IJob job, Dictionary<string, string> _dictMPs)

        {

            try

            {

                foreach (ITask task in job.Tasks)

                {

                    Console.WriteLine("Processing Task Id:" + task.Id);

 

                    // タスクに関連付けられている HistoricalEvents をループ処理して、完了済みタスクを検出する

                    // Task.State の状態はすべて Completed であり、これを利用してタスクがエラーで終了したか正常に完了したかを判断することはできない

                    for (int i = 0; i < task.HistoricalEvents.Count; i++)

                    {

                        if (task.HistoricalEvents[i].Code == "Finished")

                        {

                            try

                            {

                                JobAndTaskTableEntity tme = new JobAndTaskTableEntity();

                                tme.PartitionKey = job.Id;

                                tme.RowKey = task.Id;

                                tme.StartTime = Convert.ToDateTime(task.StartTime);

                                tme.EndTime = Convert.ToDateTime(task.EndTime);

                                tme.MediaProcessor = _dictMPs[task.MediaProcessorId]; // MediaProcessor 辞書を使用して MediaProcessorName を特定する

                                tme.RunningDuration = task.RunningDuration;

                                tme.InputAssetSize = 0;

                                tme.OutputAssetSize = 0;

 

                                for (int j = 0; j < task.InputAssets.Count; j++)

                                {

                                    tme.InputAssetSize += GetAssetSize(task.InputAssets[j]);

                                }

 

                                for (int k = 0; k < task.OutputAssets.Count; k++)

                                {

                                    tme.OutputAssetSize += GetAssetSize(task.OutputAssets[k]);

                                }

 

                                TableOperation op = TableOperation.Insert(tme);

                                _taskTable.Execute(op);

                            }

                            catch (Exception x)

                            {

                                Console.WriteLine(x.Message);

                            }

                        }

                    }

                }

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

        }

 

        /// <summary>

        /// アセット コンテナー内のすべての BLOB のリストを作成して各 BLOB のサイズを合計し、アセットのサイズを取得する

        /// </summary>

        /// <param name="_asset"></param>

        /// <returns></returns>

        static double GetAssetSize(IAsset _asset)

        {

            double _assetSize = 0;

 

            try

            {

                // アセット コンテナーの名前はアセットの GUID に "asset-" というプレフィックスを追加した文字列。アセットの ID はそのアセットの GUID に "nb:cid:UUID:" というプレフィックスを追加したもの。

                foreach (CloudBlockBlob _blobItem in _blobClient.ListBlobs("asset-" + _asset.Id.Replace("nb:cid:UUID:", "") + "/", true))

                {

                    _assetSize += _blobItem.Properties.Length;

                }

            }

            catch (Exception ex)

            {

                // ストレージ内にアセットが存在しない場合はアセットのサイズとして 0 が返される

                // タスクが完了した後でサンプル コードが実行される前にアセットが削除されると、この状況が発生する可能性がある

                Console.WriteLine(ex.Message);

            }

 

            return _assetSize;

        }       

 

    }

}

このコードで使用されている関数について簡単に説明します。

ProcessJobs

この関数は、指定された Media Services アカウントのすべてのジョブをループ処理します。Media Services は、ジョブ コレクションで 1,000 個のジョブを返します。この関数では、Skip および Take を使用して、アカウント内に 1,000 個以上のジョブが存在する場合にも確実にすべてのジョブをリストに含めます。

GetMediaProcessors

この関数は、指定された Media Services アカウントで使用可能なすべてのメディア プロセッサをループ処理し、キーが MediaProcesssorId、値が MediaProcessorName の辞書を作成します。この辞書は、JobAndTaskMetadata テーブルにエンティティを書き出すときに使用されます。

ProcessTasks

この関数は、特定のジョブに関連付けられているすべてのタスクをループ処理します。さらに、タスクに関連付けられている過去のイベントをすべてループ処理し、タスクが正常に完了したかどうかを確認します。これにより、エラーで終了したタスク (課金対象外) がエントリのログに記録されないようにします。また、完了済みタスクについて入力アセットと出力アセットのサイズを取得し、JobAndTaskMetadata テーブルのエンティティを作成します。

GetAssetSize

この関数は、ストレージ アカウントのアセット コンテナー内の BLOB をすべてループ処理し、アセットのサイズをバイト単位で計算します。

Excel の Power Query を使用してデータを分析する

上記のサンプル コードの実行が完了すると、完了済みタスクのデータについてまとめられた、JobAndTaskMetadata という名前の Azure Tables が作成されます。このデータは、Excel の Power Query を使用すると Excel にインポートして分析することができます。Excel の Power Query を使用したことのないお客様は、Microsoft Power Query for Excel のダウンロード ページ (英語) からダウンロードしてご利用ください。インストール完了後に Excel を起動すると、[POWER QUERY] というタブが表示されます。このタブをクリックし、続いて [From Other Sources] ボタンをクリックすると、次のスクリーンショットのように [From Windows Azure Table Storage] というメニュー項目 (下のスクリーンショット参照) が表示されます。

このメニュー項目を選択すると、次のダイアログ ボックスが表示されます。

ストレージ アカウント名を入力すると、次のダイアログが表示されます。

アカウント キーを入力して [Save] をクリックすると、右側に [Navigator] ウィンドウが表示されます。ストレージ アカウントのすべての Azure Tables のリストが表示され、JobAndTaskMetadata テーブルをダブル クリックすると、次のスクリーンショットのように新しいウィンドウが表示されます。

[Content] 列の隣のボタンをクリックすると、次のようなポップアップが表示されます。

そのまま [OK] をクリックすると、すべての列が読み込まれます。上部にある [Apply & Close] ボタンをクリックして、すべてのデータをワークシートに読み込みます。

ここで [Billable Gigabytes] という列を追加し、バイト単位で表示されているアセットのサイズをギガバイト単位に変更するため、InputAssetSize と OutputAssetSize の和を (1024*1024*1024) で割った値を求める式を追加します。Media Services ではメディア プロセッサが「Microsoft Azure Media Encoder」により送信されたタスクのみが課金対象であるため、それ以外の行はフィルターで除外します。

考慮事項

最後に、アプリケーションでサンプル コードを利用する際の注意事項について説明します。

  • MSDN の「クォータと制限」のページに記載されているように、Media Services アカウントではジョブ、アセット、およびタスクの最大数に制限が設けられています。ここでご紹介したサンプル コードは、指定された Media Services アカウントでアセット、ジョブ、およびタスクが削除されていないことを前提としています。しかし、アプリケーションが多数のジョブを送信する場合は、これらを削除しないままにすることは困難です。このような場合には、ジョブやアセットを削除する直前で上記のコードの ProcessTasks 関数を使用することを推奨します。
  • アプリケーションが、ジョブ完了後にアセット コンテナーでメディアの BLOB を追加または削除すると、正確な課金対象のデータ量が得られません。これを防ぐために、ジョブが完了したらすぐにそのジョブに対して ProcessTasks 関数を実行することを推奨します。ジョブ通知を使用している場合は、ジョブの状態が “Finished” になったことを示すメッセージを受け取ったらすぐに ProcessTasks 関数を呼び出すことができます。
  • この記事でご紹介したサンプル コードは、すべてのアセットが 1 つのストレージ アカウントに含まれている Media Services アカウントで使用することを前提としていますが、簡単な変更で複数のストレージ アカウントでも動作するようにできます。
  • この記事のサンプル コードでは、InputAssetSize と OutputAssetSize はバイト単位の値を書き出します。このコードがギガバイト単位の値を書き出すように変更することもできますし、さらに JobAndTaskMetadata テーブルに新しい列を追加して InputAssetSize と OutputAssetSize の合計を書き出すことも可能です。
  • JobAndTaskMetadata テーブルのデータを表示するには、Microsoft Azure 用の各種エクスプローラーのうち Azure Tables をサポートしている既存のものを使用する方法と、Excel を使用する方法があります。Excel では、ピボット テーブルなどの強力なデータ操作機能を使用できるので、データを詳細に分析することができます。
  • 毎月の Azure の料金請求は、請求の応答日およびアカウントに適用される割引 (大口値引きやコミットメント割引) に基づいて計算されます。この記事のサンプル コードの結果と Azure の月間請求額を比較する場合はこの点にご注意ください。
  • この記事のサンプル コードには、副次的な効果として、Media Encoder 以外のメディア プロセッサで処理されたデータ量をワークシートに読み込まれたデータから確認できます。
Comments (0)

Skip to main content