Skip to main content

Daily health checks, 2/3

A

As a continuation of the first article, today I will focus mainly on APEX-related checks. To avoid one huge email, this part covers APEX-related checks, which you can find in the SEND_APPS procedure. Let's get started.

I usually ignore all working copies, but even with this approach you might still have some leftover apps in your environment. So, in the CORE_CUSTOM package (under g_apps), you can limit the scope of your apps.

This might be interesting: how would you select the same specific apps in multiple queries (without hardcoding the values in more than one place)? You declare the list in the package specification and create a function so you can use it in any statement:

    g_apps apex_t_varchar2 := apex_t_varchar2(
        100,
        110,
        120,
        ...
    );

    FUNCTION get_apps
    RETURN apex_t_varchar2
    AS
    BEGIN
        RETURN core_custom.g_apps;
    END;

Then you can use it like this:

SELECT t.*
FROM apex_applications t
JOIN TABLE (core_reports.get_apps()) f
    ON TO_NUMBER(f.column_value) = t.application_id;

Spoiler alert: there is no more technical magic in this article. I will just cover related views.


What to check?


APEX apps overview – to see the latest updates, version numbers, detect changes (especially on non-dev environments), and check if app scans have stalled:

SELECT
    t.application_group     AS app_group,
    t.application_id        AS app_id,
    t.application_name      AS app_name,
    t.version,
    --t.build_status,
    t.pages,
    t.last_updated_by       AS updated_by,
    t.last_updated_on       AS updated_at,
    --
    COALESCE (
        TO_CHAR(t.last_dependency_analyzed_at, 'YYYY-MM-DD HH24:MI'),
        CASE WHEN f.column_value IS NOT NULL THEN 'MISSING' END
    ) AS analyzed_at
    --
FROM apex_applications t
LEFT JOIN TABLE (core_reports.get_apps()) f
    ON TO_NUMBER(f.column_value)    = t.application_id
WHERE t.is_working_copy             = 'No'
    AND t.application_group         NOT LIKE '\_\_%' ESCAPE '\'
ORDER BY
    1, 2;


Traffic Today – to get a high-level picture of app usage (requests, users, sessions, errors, etc.):

SELECT
    t.application_id        AS app_id,
    t.application_name      AS app_name,
    --
    NULLIF(a.page_views, 0)         AS page_views,
    NULLIF(a.page_events, 0)        AS page_events,
    NULLIF(a.distinct_pages, 0)     AS pages_,
    NULLIF(a.distinct_users, 0)     AS users_,
    NULLIF(a.distinct_sessions, 0)  AS sessions_,
    NULLIF(a.error_count, 0)        AS errors_,
    ROUND(a.maximum_render_time, 2) AS render_time_max
    --
FROM apex_applications t
LEFT JOIN TABLE (core_reports.get_apps()) f
    ON TO_NUMBER(f.column_value)    = t.application_id
JOIN apex_workspace_log_archive a
    ON a.application_id             = t.application_id
    AND a.log_day                   = core_reports.get_start_date()
WHERE t.is_working_copy             = 'No'
    AND t.application_group         NOT LIKE '\_\_%' ESCAPE '\'
ORDER BY
    1, 2;


Workspace Files – to detect changes in workspace files:

SELECT
    f.file_name,
    DBMS_LOB.GETLENGTH(f.file_content) AS file_size,
    f.last_updated_by   AS updated_by,
    f.last_updated_on   AS updated_at
    --
FROM apex_workspace_static_files f
WHERE (
        f.file_name     LIKE '%.css'
        OR f.file_name  LIKE '%.js'
    )
    AND f.file_name     NOT LIKE '%.min.%'
ORDER BY 1;


Developers Timeline – for an overview of developer activity in APEX Builder (not inside the apps). Note that this does not necessarily reflect whether developers are actively working:

SELECT
    l.apex_user AS user_id,
    --
    COUNT(*) AS total,
    --
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '00' THEN 1 END), 0) AS "00",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '01' THEN 1 END), 0) AS "01",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '02' THEN 1 END), 0) AS "02",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '03' THEN 1 END), 0) AS "03",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '04' THEN 1 END), 0) AS "04",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '05' THEN 1 END), 0) AS "05",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '06' THEN 1 END), 0) AS "06",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '07' THEN 1 END), 0) AS "07",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '08' THEN 1 END), 0) AS "08",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '09' THEN 1 END), 0) AS "09",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '10' THEN 1 END), 0) AS "10",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '11' THEN 1 END), 0) AS "11",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '12' THEN 1 END), 0) AS "12",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '13' THEN 1 END), 0) AS "13",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '14' THEN 1 END), 0) AS "14",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '15' THEN 1 END), 0) AS "15",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '16' THEN 1 END), 0) AS "16",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '17' THEN 1 END), 0) AS "17",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '18' THEN 1 END), 0) AS "18",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '19' THEN 1 END), 0) AS "19",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '20' THEN 1 END), 0) AS "20",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '21' THEN 1 END), 0) AS "21",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '22' THEN 1 END), 0) AS "22",
    NULLIF(COUNT(CASE WHEN TO_CHAR(l.view_timestamp, 'HH24') = '23' THEN 1 END), 0) AS "23"
    --
FROM apex_workspace_activity_log l
LEFT JOIN apex_applications a
    ON a.application_id = l.application_id
WHERE 1 = 1
    AND a.application_id    IS NULL
    AND l.apex_user         NOT LIKE '%MONITOR%'
    --
    AND TO_CHAR(l.view_timestamp, 'YYYY-MM-DD') = core_reports.get_start_date()
GROUP BY ALL
ORDER BY 1;


Component Changes – to see what changed that day in the app (by developer and page):

SELECT
    g.developer,
    g.page_id,
    g.page_name,
    --
    COUNT(DISTINCT g.component_id) AS components,
    --
    NULLIF(COUNT(CASE WHEN g.audit_action = 'Insert' THEN 1 END), 0) AS inserted_,
    NULLIF(COUNT(CASE WHEN g.audit_action = 'Update' THEN 1 END), 0) AS updated_,
    NULLIF(COUNT(CASE WHEN g.audit_action = 'Delete' THEN 1 END), 0) AS deleted_
    --
FROM apex_developer_activity_log g
WHERE 1 = 1
    AND g.application_id    = core.get_app_id()
    AND g.audit_date        >= core_reports.get_start_date()
    AND g.audit_date        <  core_reports.get_end_date()
    AND g.developer         != USER
GROUP BY ALL
ORDER BY
    1, 2, 3;


Performance – to check how individual pages performed that day:

WITH t AS (
    SELECT
        a.id,
        --
        CASE WHEN GROUPING_ID(a.id) = 0 THEN a.application_id       END AS application_id,
        CASE WHEN GROUPING_ID(a.id) = 0 THEN a.page_id              END AS page_id,
        CASE WHEN GROUPING_ID(a.id) = 0 THEN a.page_name            END AS page_name,
        CASE WHEN GROUPING_ID(a.id) = 0 THEN a.view_date            END AS view_date,
        CASE WHEN GROUPING_ID(a.id) = 0 THEN MAX(a.page_view_type)  END AS page_view_type,
        --
        SUM(a.elapsed_time) AS elapsed_time,
        --
        COUNT(DISTINCT a.apex_user) AS count_users
        --
    FROM (
        SELECT
            CASE WHEN page_view_type = 'Rendering' THEN a.id
                ELSE LAG(CASE WHEN page_view_type = 'Rendering' THEN a.id END IGNORE NULLS) OVER (ORDER BY a.id)
                END AS request_id,
            a.id,
            a.application_id,
            a.page_id,
            a.page_name,
            a.apex_user,
            a.view_date,
            a.elapsed_time,
            a.page_view_type,
            a.view_timestamp,
            a.apex_session_id
        FROM apex_workspace_activity_log a
        WHERE 1 = 1
            AND a.application_id    = core.get_app_id()
            AND a.view_date         >= core_reports.get_start_date()
            AND a.view_date         <  core_reports.get_end_date()
    ) a
    GROUP BY
        a.id,
        a.request_id,
        a.application_id,
        a.page_id,
        a.page_name,
        a.view_date
    HAVING a.request_id IS NOT NULL
)
SELECT
    t.application_id AS app_id,
    t.page_id,
    t.page_name,
    MAX(t.count_users) AS users_,
    --
    NULLIF(COUNT(CASE WHEN t.page_view_type = 'Rendering'   THEN t.id END), 0)              AS rendering_count,
    ROUND(AVG(   CASE WHEN t.page_view_type = 'Rendering'   THEN t.elapsed_time END), 2)    AS rendering_avg,
    ROUND(MAX(   CASE WHEN t.page_view_type = 'Rendering'   THEN t.elapsed_time END), 2)    AS rendering_max,
    --
    NULLIF(COUNT(CASE WHEN t.page_view_type = 'Processing'  THEN t.id END), 0)              AS processing_count,
    ROUND(AVG(   CASE WHEN t.page_view_type = 'Processing'  THEN t.elapsed_time END), 2)    AS processing_avg,
    ROUND(MAX(   CASE WHEN t.page_view_type = 'Processing'  THEN t.elapsed_time END), 2)    AS processing_max,
    --
    NULLIF(COUNT(CASE WHEN t.page_view_type = 'Ajax'        THEN t.id END), 0)              AS ajax_count,
    ROUND(AVG(   CASE WHEN t.page_view_type = 'Ajax'        THEN t.elapsed_time END), 2)    AS ajax_avg,
    ROUND(MAX(   CASE WHEN t.page_view_type = 'Ajax'        THEN t.elapsed_time END), 2)    AS ajax_max
    --
FROM t
GROUP BY ALL;


Requests History – to track user activity over the past two weeks (this is just a snippet for brevity; the real view pivots the data):

    SELECT
        a.application_id,
        a.page_id,
        a.page_name,
        a.view_date,
        --
        NULLIF(SUM(CASE WHEN a.page_view_type = 'Rendering' THEN 1 ELSE 0 END), 0)      AS rendering,
        NULLIF(SUM(CASE WHEN a.page_view_type = 'Processing' THEN 1 ELSE 0 END), 0)     AS processing,
        NULLIF(SUM(CASE WHEN a.page_view_type = 'Ajax' THEN 1 ELSE 0 END), 0)           AS ajax,
        --
        COUNT(*)                            AS activity,
        COUNT(DISTINCT a.apex_user)         AS users,
        COUNT(DISTINCT a.apex_session_id)   AS sessions,
        --
        ROUND(AVG(a.elapsed_time),2)        AS avg_time,
        ROUND(MAX(a.elapsed_time), 2)       AS max_time
    FROM (
        SELECT
            a.application_id,
            a.page_id,
            a.page_name,
            TRUNC(a.view_date) AS view_date,
            a.page_view_type,
            a.elapsed_time,
            a.apex_user,
            a.apex_session_id
        FROM apex_workspace_activity_log a
        WHERE a.application_id = core.get_app_id()
    ) a
    GROUP BY
        a.application_id,
        a.page_id,
        a.page_name,
        a.view_date;


And finally, not per app, but as a good wrap-up: the REST services...

REST Services Overview – with source code and arguments check (so if you have the source code in packages, as you should, it verifies this, ensures all procedure arguments are covered by bind variables, and checks that all bind variables in the service are used). This query is a bit longer, so check the source code – it is full of magic.


Comments