?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE Accounting and Validation: Job Accounting Kernel' ??
MODULE avm$job_accounting_kernel;
?? RIGHT := 110 ??

{ PURPOSE:
{   The purpose of this module is to provide accounting for job usage of system
{   resource units, and to update statistics accumulated by MONITOR regarding the
{   currently executing job.  This module executes in ring 2.
{
{ DESIGN:
{   This module contains three main procedures which are called during the three
{   phases of a job.  The first procedure is called to initiate a new period of
{   accountability, and establish those statistics used to calculate system
{   resource units.  The second procedure is a flag handler which is executed
{   intermittently during the job to update the CP time and SRU accumulators for
{   the job.  The third procedure is called to end the current period of
{   accountability.

?? NEWTITLE := 'Global Declarations referenced by this module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc avc$accounting_statistics
*copyc avc$system_defined_limit_names
*copyc avt$account_name
*copyc avt$sru_calculation_interval
*copyc avt$project_name
*copyc oss$job_pageable
*copyc ost$name
*copyc ost$status
*copyc ost$system_flag
*copyc ost$user_identification
*copyc sfc$unlimited
*copyc sfc$warning_grace_period
?? POP ??
*copyc avp$calculate_srus
*copyc avp$update_eoj_total_limits
*copyc clp$trimmed_string_size
*copyc jmp$determine_job_class_name
*copyc jmp$system_job
*copyc osp$clear_job_signature_lock
*copyc osp$initialize_sig_lock
*copyc osp$set_job_signature_lock
*copyc osp$test_signature_lock
*copyc sfp$change_file_space_limit
*copyc sfp$create_job_limit
*copyc sfp$emit_statistic
*copyc sfp$initiate_resource_condition
*copyc sfp$job_limit_chain_entry
*copyc sfp$update_job_limit_accum
*copyc tmp$fetch_job_statistics
*copyc tmp$set_flag_interval
*copyc avv$validated_limits
*copyc jmv$jcb
*copyc sfv$dynamic_file_space_limits
?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations declared by this module', EJECT ??

  VAR
    avv$monitor_statistics_lock: [XDCL, STATIC, oss$job_pageable] ost$signature_lock,
    avv$accumulated_srus: [XDCL, #GATE, STATIC, oss$job_pageable] sft$counter := 0,
    end_account_called: [STATIC, oss$job_pageable] boolean := FALSE;

?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] avp$begin_account', EJECT ??
*copyc avh$begin_account

  PROCEDURE [XDCL, #GATE] avp$begin_account
    (    family_name: ost$family_name;
         user_name: ost$user_name;
         account_name: avt$account_name;
         project_name: avt$project_name;
         user_supplied_job_name: ost$name;
         job_class: jmt$job_class;
     VAR status: ost$status);

    VAR
      descriptive_data: string (osc$max_string_size),
      descriptive_data_size: integer,
      index: integer,
      job_class_name: jmt$job_class_name,
      statistic_codes: ^array [1 .. * ] of sft$statistic_code,
      validated_limit: ^avt$validated_limit;

    status.normal := TRUE;
    osp$initialize_sig_lock (avv$monitor_statistics_lock);

{ Set the interval used to trigger SRU calulation to unlimited.

    tmp$set_flag_interval (0ffffffff(16));

{ Emit the begin account statistic.
    jmp$determine_job_class_name (job_class, job_class_name, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

    STRINGREP (descriptive_data, descriptive_data_size, family_name
          (1, clp$trimmed_string_size (family_name)), ', ', user_name
          (1, clp$trimmed_string_size (user_name)), ', ', account_name
          (1, clp$trimmed_string_size (account_name)), ', ',
          project_name (1, clp$trimmed_string_size (project_name)),
          ', ', user_supplied_job_name (1, clp$trimmed_string_size (user_supplied_job_name)), ', ',
          job_class_name (1, clp$trimmed_string_size (job_class_name)));

    sfp$emit_statistic (avc$begin_account, descriptive_data (1, descriptive_data_size), NIL, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

{ Set up the limits for the job based on the limit information determined by validation.

    validated_limit := avv$validated_limits;
    WHILE validated_limit <> NIL DO
      IF validated_limit^.kind = avc$accumulating_limit_kind THEN
        IF validated_limit^.statistic_codes = NIL THEN
          statistic_codes := NIL;
        ELSE
          PUSH statistic_codes: [1 .. UPPERBOUND (validated_limit^.statistic_codes^)];
          FOR index := 1 TO UPPERBOUND (validated_limit^.statistic_codes^) DO
            statistic_codes^ [index] := validated_limit^.statistic_codes^ [index].statistic_code;
          FOREND;
        IFEND;
        sfp$create_job_limit (validated_limit^.limit_name, statistic_codes, validated_limit^.initial_value,
              validated_limit^.job_warning_limit, validated_limit^.job_maximum_limit,
              validated_limit^.enforcement, status);
        IF NOT status.normal THEN
          RETURN; {----->
        IFEND;

{ If the limit being set up is either the permanent file space or temporary file space limits, initialize the
{ associated limit values and counters.

        IF validated_limit^.limit_name = avc$pfs_limit_name THEN
          sfp$change_file_space_limit (sfc$perm_file_space_limit, ^validated_limit^.job_warning_limit,
                ^validated_limit^.job_maximum_limit, ^validated_limit^.initial_value,
                {Job warning checking} NIL);
        ELSEIF validated_limit^.limit_name = avc$tfs_limit_name THEN
          sfp$change_file_space_limit (sfc$temp_file_space_limit, ^validated_limit^.job_warning_limit,
                ^validated_limit^.job_maximum_limit, ^validated_limit^.initial_value,
                {Job warning checking} NIL);
        IFEND;
      IFEND;
      validated_limit := validated_limit^.forward;
    WHILEND;

{ Update the CPU time and SRU accumulators.  This will reset the interval timer used to trigger the next SRU
{ calculation.

    avp$monitor_statistics_handler (avc$monitor_statistics_flag);

  PROCEND avp$begin_account;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] avp$end_account', EJECT ??
*copyc avh$end_account

  PROCEDURE [XDCL, #GATE] avp$end_account
    (VAR status: ost$status);

    VAR
      cpu_time: sft$counter,
      end_account_counters: array [1 .. 9] of sft$counter,
      ignore_status: ost$status,
      job_statistics: jmt$job_statistics,
      sru_calculation_interval: avt$sru_calculation_interval,
      sru_limit: ^sft$limit_chain_entry,
      srus: sft$counter;

{ Get the latest CPU time and page fault statistics for the job.
    tmp$fetch_job_statistics (job_statistics, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

    cpu_time := job_statistics.cp_time.time_spent_in_job_mode + job_statistics.cp_time.time_spent_in_mtr_mode;

{ Update the CPU time limit accumulator.

    sfp$update_job_limit_accum (avc$cpu_time_limit_name, (cpu_time DIV 1000000), sfc$replacement_update,
          status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

{ Calculate a new total for the SRU accumulator.

    sru_limit := sfp$job_limit_chain_entry (avc$sru_limit_name);
    IF sru_limit = NIL THEN
      RETURN; {----->
    IFEND;

    avp$calculate_srus (job_statistics, sru_limit^.limit, avv$accumulated_srus, sru_calculation_interval,
          status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

{ Update the SRU limit accumulator.
    sfp$update_job_limit_accum (avc$sru_limit_name, (avv$accumulated_srus DIV 1000000),
          sfc$replacement_update, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

{ Turn off monitor statistics handler so that accounting and limits match 100%.

    end_account_called := TRUE;

{ Emit the end account statistic.
    end_account_counters [1] := avv$accumulated_srus;
    end_account_counters [2] := job_statistics.cp_time.time_spent_in_job_mode;
    end_account_counters [3] := job_statistics.cp_time.time_spent_in_mtr_mode;
    end_account_counters [4] := job_statistics.paging_statistics.page_fault_count;
    end_account_counters [5] := job_statistics.paging_statistics.page_in_count;
    end_account_counters [6] := job_statistics.paging_statistics.pages_reclaimed_from_queue;
    end_account_counters [7] := job_statistics.paging_statistics.new_pages_assigned;
    end_account_counters [8] := job_statistics.paging_statistics.pages_from_server;
    end_account_counters [9] := job_statistics.paging_statistics.working_set_max_used;

    sfp$emit_statistic (avc$end_account, '', ^end_account_counters, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

{ Update any total limit accumulators in the validation file.
    avp$update_eoj_total_limits (status);

  PROCEND avp$end_account;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] avp$monitor_statistics_handler', EJECT ??
*copyc avh$monitor_statistics_flag

  PROCEDURE [XDCL, #GATE] avp$monitor_statistics_handler
    (    flag_id: ost$system_flag);

    VAR
      calculation_interval: avt$sru_calculation_interval,
      cpu_time: sft$counter,
      cpu_time_limit: ^sft$limit_chain_entry,
      fake_sru_limit: sft$limit,
      ignore_status: ost$status,
      job_statistics: jmt$job_statistics,
      limit_chain_entry: ^sft$limit_chain_entry,
      local_status: ost$status,
      lock_status: ost$signature_lock_status,
      remaining_cpu_time: sft$counter,
      sru_calculation_interval: avt$sru_calculation_interval,
      srus: sft$counter,
      sru_limit: ^sft$limit_chain_entry,
      warning: boolean;

{ No action is necessary once end account has been called.

    IF end_account_called THEN
      RETURN; {----->
    IFEND;

{ Check if this routine is already running somewhere else in the job.
    osp$test_signature_lock (avv$monitor_statistics_lock, lock_status);
    IF lock_status <> osc$sls_not_locked THEN
      RETURN; {----->
    IFEND;

{ Set the lock to prevent other occurrences of this routine from running somewhere else in the job.
    osp$set_job_signature_lock (avv$monitor_statistics_lock);

  /monitor_statistics_handler/
    BEGIN

{ Get the latest CPU time and page fault statistics for the job.

      tmp$fetch_job_statistics (job_statistics, local_status);
      IF NOT local_status.normal THEN
        EXIT /monitor_statistics_handler/; {----->
      IFEND;

      IF jmp$system_job () THEN

{ The system job does not have any limits, so this routine calls AVP$CALCULATE_SRUS
{ (with fake SRU limit information) so that the variable AVV$ACCUMULATED_SRUS is
{ updated.

        fake_sru_limit.name := avc$sru_limit_name;
        fake_sru_limit.condition_identifier := 0;
        fake_sru_limit.accumulator := avv$accumulated_srus DIV 1000000;
        fake_sru_limit.job_resource_limit := sfc$unlimited;
        fake_sru_limit.job_abort_limit := sfc$unlimited;
        fake_sru_limit.enforcement := sfc$accumulation_enforcement;

        avp$calculate_srus (job_statistics, fake_sru_limit, avv$accumulated_srus, sru_calculation_interval,
              local_status);
        IF NOT local_status.normal THEN
          EXIT /monitor_statistics_handler/; {----->
        IFEND;

        IF sru_calculation_interval < UPPERVALUE (calculation_interval) THEN
          calculation_interval := sru_calculation_interval;
        ELSE
          calculation_interval := UPPERVALUE (calculation_interval);
        IFEND;
      ELSE

{ If not executing in the system job, CP_TIME and SRU limits should exist and must be updated.  A new flag
{ interval is selected to insure that the CP_TIME limit and the SRU limit are not over run before the next
{ call to this routine.

        cpu_time_limit := sfp$job_limit_chain_entry (avc$cpu_time_limit_name);
        IF cpu_time_limit = NIL THEN
          EXIT /monitor_statistics_handler/; {----->
        IFEND;

        sru_limit := sfp$job_limit_chain_entry (avc$sru_limit_name);
        IF sru_limit = NIL THEN
          EXIT /monitor_statistics_handler/; {----->
        IFEND;

{ Calculate a new value for the CP_TIME limit accumulator -- fractions of a CP second
{ are ignore.

        cpu_time := (job_statistics.cp_time.time_spent_in_job_mode +
              job_statistics.cp_time.time_spent_in_mtr_mode) DIV 1000000;

{ Update the CPU_TIME accumulator.

        sfp$update_job_limit_accum (avc$cpu_time_limit_name, cpu_time, sfc$replacement_update, ignore_status);

{ Calculate a new total for the SRU accumulator.

        avp$calculate_srus (job_statistics, sru_limit^.limit, avv$accumulated_srus, sru_calculation_interval,
              local_status);
        IF NOT local_status.normal THEN
          EXIT /monitor_statistics_handler/; {----->
        IFEND;

{ Update the SRU accumulator.

        sfp$update_job_limit_accum (avc$sru_limit_name, (avv$accumulated_srus DIV 1000000),
              sfc$replacement_update, ignore_status);

{ Compute a new calculation interval.

        remaining_cpu_time := cpu_time_limit^.limit.job_resource_limit - cpu_time;

        IF remaining_cpu_time < 0 THEN

{ The job has exceeded its job warning limit for CPU time.  Compute a new calculation interval that is the
{ smaller of the remining time until the job maximum limit is hit or the resource warning grace period (making
{ sure that the computed interval is not less than the minimum calculation interval).

          remaining_cpu_time := cpu_time_limit^.limit.job_abort_limit - cpu_time;
          IF remaining_cpu_time <= LOWERVALUE (calculation_interval) THEN
            calculation_interval := LOWERVALUE (calculation_interval);
          ELSEIF remaining_cpu_time >= sfc$warning_grace_period THEN
            calculation_interval := sfc$warning_grace_period;
          ELSE
            calculation_interval := remaining_cpu_time;
          IFEND;
        ELSE

{ Compute a calculation interval that is one half of the remaining time until the job hits the job warning
{ limit (making sure that the computed value is in the allowed range for the calculation interval).

          IF (remaining_cpu_time DIV 2) >= UPPERVALUE (calculation_interval) THEN
            calculation_interval := UPPERVALUE (calculation_interval);
          ELSEIF (remaining_cpu_time DIV 2) <= LOWERVALUE (calculation_interval) THEN
            calculation_interval := LOWERVALUE (calculation_interval);
          ELSE
            calculation_interval := remaining_cpu_time DIV 2;
          IFEND;
        IFEND;

{ Select the smaller of the SRU calculation interval or the CPU time calculation interval.

        IF sru_calculation_interval < calculation_interval THEN
          calculation_interval := sru_calculation_interval;
        IFEND;

{ Check permanent and temporary file space warning limits.

        IF sfv$dynamic_file_space_limits THEN
          IF (jmv$jcb.ijle_p^.statistics.perm_file_space >= jmv$jcb.perm_file_job_warning_limit) AND
                jmv$jcb.perm_file_job_warning_checking THEN
            warning := FALSE;
            sfp$change_file_space_limit (sfc$perm_file_space_limit, {warning_limit = } NIL,
                  {job_maximum_limit = } NIL, {accumulator = } NIL, {job_warning_checking = } ^warning);
            limit_chain_entry := sfp$job_limit_chain_entry (avc$pfs_limit_name);
            IF limit_chain_entry <> NIL THEN
              sfp$initiate_resource_condition (limit_chain_entry, ignore_status);
            IFEND;
          ELSEIF (jmv$jcb.ijle_p^.statistics.temp_file_space >= jmv$jcb.temp_file_job_warning_limit) AND
                jmv$jcb.temp_file_job_warning_checking THEN
            warning := FALSE;
            sfp$change_file_space_limit (sfc$temp_file_space_limit, {warning_limit = } NIL,
                  {job_maximum_limit = } NIL, {accumulator = } NIL, {job_warning_checking = } ^warning);
            limit_chain_entry := sfp$job_limit_chain_entry (avc$tfs_limit_name);
            IF limit_chain_entry <> NIL THEN
              sfp$initiate_resource_condition (limit_chain_entry, ignore_status);
            IFEND;
          IFEND;
        IFEND;
      IFEND;

{ Reset the interval used to trigger the next SRU calculation.
{ Guarantee a non-zero value.

      tmp$set_flag_interval ((calculation_interval * 1000000) + 1000);

    END /monitor_statistics_handler/;

    osp$clear_job_signature_lock (avv$monitor_statistics_lock);

  PROCEND avp$monitor_statistics_handler;
?? OLDTITLE ??
MODEND avm$job_accounting_kernel;
