/**
 * @file task_perf.c
 * @author Shinichiro Nakamura
 * @brief 性能計測タスクの実装。
 */

/*
 * ===============================================================
 *  LPCXpresso Clock
 * ===============================================================
 * Copyright (c) 2010-2011 Shinichiro Nakamura
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * ===============================================================
 */

#include <kernel.h>
#include <t_syslog.h>
#include <t_stdlib.h>
#include "kernel_cfg.h"
#include "task_perf.h"

#define SAMPLE_UNIT_MS      (5)     /**< サンプル単位時間。 */
#define REPORT_INTERVAL_MS  (200)   /**< リポート間隔。 */
#define CONSOLE_OUTPUT      (0)     /**< コンソールに出力する場合は1 */

/*
 * 性能計測タスクはシステムの負荷状態を検出することのできるタスクである。
 * 検出は以下のようにして行なう。
 *
 * 1. システム起動時、このタスクだけが稼働し、
 *    単位時間に何回の加算命令が実行できるのかを求める。
 *    これをシステムのCPU使用率0%時に得られるカウンタ値とする。
 * 2. システムの他のタスクが起動した後、
 *    単位時間に何回の加算命令が実行できるのかを求める。
 *    これを現在のCPU使用率時に得られるカウンタ値とする。
 * 3. 上記の結果を合わせて、システムの負荷が何パーセントなのかを求める。
 *
 * このタスクは、システム内の他のタスクよりも低い優先度で動作させること。
 * このタスクは、このタスクよりも高い優先度のタスクの処理に時間を奪われる
 * 事を利用してCPU使用率を算出する仕組みである。
 *
 * リアルタイムシステムでは、イベント駆動で設計される。
 * 万が一、その他のタスクが高優先度で単純ループ処理などを行なう場合、
 * このタスクによる性能計測はうまくいかないだろう。
 */

void task_perf_handler_alm(intptr_t exinf)
{
    /*
     * セマフォを返却する。
     */
    isig_sem(SEM_PERF);
}

/**
 * @brief システムのパフォーマンスを計測する。
 */
static void measure_system_performance(unsigned int *cnt)
{
    /*
     * セマフォを取得する。
     */
    wai_sem(SEM_PERF);

    /*
     * アラームを開始する。
     */
    sta_alm(ALM_PERF, SAMPLE_UNIT_MS);

    /*
     * カウンタをリセットしてからカウントアップを実行する。
     */
    *cnt = 0;
    do {
        (*cnt)++;
    } while (pol_sem(SEM_PERF) != E_OK);

    /*
     * セマフォを返却する。
     */
    sig_sem(SEM_PERF);
}

/**
 * @brief パフォーマンスを計算する。
 * @param cnt_cur 現在のパフォーマンスカウンタ値。
 * @param cnt_max 最大パフォーマンスカウンタ値。
 * @return CPU使用率。
 */
static int calc_performance(const int cnt_cur, const int cnt_max)
{
    int np = cnt_cur * 100 / cnt_max;
    np = (np > 100) ? 100 : np;
    return (100 - np);
}

/**
 * @brief 性能計測タスクの実装。
 */
void task_perf(intptr_t exinf)
{
    unsigned int cnt_max = 0;    /**< 最大カウント。 */
    unsigned int cnt_cur = 0;    /**< 現在カウント。 */
    unsigned int reports = 0;    /**< レポートカウンタ。 */

    syslog(LOG_NOTICE, "Performance measurement task initialized.");

    /*
     * システム開始時にはこのタスクだけを起動する。
     *
     * 単独タスクの状態で得られる計測結果を、
     * システムで得られる最大パフォーマンスと仮定する。
     */
    measure_system_performance(&cnt_max);

    /*
     * 最大パフォーマンスを計測したので初期化タスクを起動する。
     */
    act_tsk(TASK_INIT);

    /*
     * システム稼働状態でのパフォーマンスを随時計測する。
     */
    while (1) {
        /*
         * 現在の状態でのパフォーマンスを計測する。
         */
        measure_system_performance(&cnt_cur);

        /*
         * CPU使用率の概算を得る。
         */
        if ((reports % (REPORT_INTERVAL_MS / SAMPLE_UNIT_MS)) == 0) {
#if (CONSOLE_OUTPUT)
            int np = calc_performance(cnt_cur, cnt_max);
            syslog(LOG_NOTICE, "[%d%%]", np);
#else
            /*
             * 何らかの方法でリポートの必要があれば、ここに実装する。
             */
#endif
        }
        reports++;
    }

    syslog(LOG_NOTICE, "Performance measurement task terminated.");
    ext_ker();
    assert(0);
}

