#!/bin/bash ################################################################################ # Script Name: jira-metrics.sh # Version: 1.4 # Author: Phil Connor # License: MIT # Description: Prometheus exporter for Jira metrics via REST API # # Exports metrics for: # - Issue counts by status (To Do, In Progress, Done, etc.) # - Issue counts by priority (Highest, High, Medium, Low, Lowest) # - Issue counts by type (Bug, Story, Task, Epic, Sub-task) # - Issue counts by project # - Backlog size and age (unresolved issues) # - Created vs resolved issue rates (24h, 7d) # - Average resolution time (resolved last 7d) # - Overdue issues count # - Sprint active issue counts (if boards configured) # - Component issue counts # - Assignee workload (top 15) # - Unassigned issue count # - SLA/resolution time buckets # - Login activity from security log (logins, failed logins, per-user) # - Plugin/add-on inventory (installed, enabled, disabled, user-installed) # - Service Desk queues, request types, SLA metrics, CSAT, organizations # - Scrape metadata # # Modes: # --textfile Write to node_exporter textfile collector # --http Run HTTP server for direct Prometheus scraping # stdout Default: print metrics to stdout # # Requirements: # - curl and jq must be available # - Jira base URL, username, and API token (or password) # - For Jira Cloud: use email + API token # - For Jira Server/DC: use username + password or PAT # # Configuration: # Set environment variables or edit the defaults below: # JIRA_BASE_URL - Full URL including port # e.g. https://yourcompany.atlassian.net (Cloud) # e.g. http://localhost:8080 (Server/DC) # JIRA_USER - e.g. user or user@company.com (not needed for PAT) # JIRA_API_TOKEN - API token, password, or Personal Access Token (PAT) # JIRA_PROJECTS - Comma-separated project keys (default: all) # JIRA_HOME - Jira home/data directory (for security log parsing) # e.g. /mnt/ebs/application-data/jira # # Authentication: # Uses Bearer token authentication (Authorization: Bearer ). # - Jira Cloud: Generate API token at # https://id.atlassian.com/manage-profile/security/api-tokens # - Jira Server/DC: Generate a PAT via Profile → Personal Access Tokens # (requires Jira 8.14+) # # Troubleshooting: # All metrics return zero: # 1. Verify JIRA_BASE_URL includes the port (e.g. http://localhost:8080, # NOT http://localhost). Test with: # curl -H "Authorization: Bearer $JIRA_API_TOKEN" \ # "$JIRA_BASE_URL/rest/api/2/search?jql=&maxResults=0" # You should see a JSON response with "total" > 0. # 2. If the above returns blank, the URL or port is wrong. # 3. If it returns HTML or a 404, check for a context path # (e.g. http://localhost:8080/jira). # # 403 Forbidden / AUTHENTICATION_DENIED: # - Basic auth may be blocked. This script uses Bearer token auth # which requires a PAT (Server/DC) or API token (Cloud). # - CAPTCHA may be triggered from prior failed logins. Clear it via # Jira Admin → User Management → find user → Reset failed login count. # - Ensure the X-Atlassian-Token: no-check header is present (included # by default in this script). # # 401 Unauthorized: # - PAT may be expired or revoked. Generate a new one. # - Verify the token is correct (no trailing whitespace/newline). # # Partial metrics (some sections zero): # - The user associated with the PAT must have Browse Projects # permission on the target projects. # - Login metrics require JIRA_HOME to be set correctly and the # script must have read access to the Jira security log at # $JIRA_HOME/log/atlassian-jira-security.log # - Service Desk metrics require Jira Service Management. # # Login metrics all zero: # - Verify JIRA_HOME points to the correct Jira data directory. # - Check the security log exists: # ls $JIRA_HOME/log/atlassian-jira-security.log # - Ensure the user running this script can read the log file. # - Note: The audit log REST API (/rest/api/2/auditing/record) # does not exist on Jira 10.x. This script parses the security # log file directly instead. # # Changelog: # 1.4 - Auto-discover all projects when JIRA_PROJECTS is not set # Fixed per-project metrics (HELP/TYPE headers always emitted) # Fixed missing metrics: all HELP/TYPE headers now always present # even when data is empty (assignee, component, plugin, service # desk queue/org, user logins, resolution time) # Per-project metrics now grouped by metric name per Prometheus # exposition format (HELP/TYPE then all values, not interleaved) # Switched login metrics from audit log REST API (404 on Jira 10.x) # to parsing $JIRA_HOME/log/atlassian-jira-security.log directly # Added JIRA_HOME configuration variable and --jira-home CLI option # Added troubleshooting section for login metrics # 1.3 - Switched authentication from basic auth to Bearer token (PAT) # Fixed JQL URL encoding (use curl --data-urlencode) # Added troubleshooting documentation # 1.2 - Added plugin/add-on inventory metrics from UPM API # Added Service Desk metrics (queues, requests, SLAs, CSAT, # request types, organizations) # 1.1 - Added login activity metrics from Jira audit log # (total logins, failed logins, unique users, per-user counts) # 1.0 - Initial release ################################################################################ SCRIPT_VERSION="1.4" TEXTFILE_DIR="/var/lib/node_exporter" OUTPUT_FILE="" HTTP_MODE=false HTTP_PORT=9418 LOCK_FILE="/var/run/jira-metrics.lock" # Jira connection settings (edit these or override via environment variables) JIRA_BASE_URL="${JIRA_BASE_URL:-https://yourcompany.atlassian.net}" JIRA_USER="${JIRA_USER:-user@company.com}" JIRA_API_TOKEN="${JIRA_API_TOKEN:-your-api-token-here}" JIRA_PROJECTS="${JIRA_PROJECTS:-}" # comma-separated, empty = all (e.g. "PROJ1,PROJ2") JIRA_HOME="${JIRA_HOME:-/mnt/ebs/application-data/jira}" # Jira home directory (for log parsing) # Timeouts CURL_TIMEOUT=30 MAX_RESULTS=1000 show_usage() { cat </dev/null 2>&1; then echo "ERROR: curl is required" >&2; fail=1 fi if ! command -v jq >/dev/null 2>&1; then echo "ERROR: jq is required" >&2; fail=1 fi if [ -z "$JIRA_BASE_URL" ]; then echo "ERROR: JIRA_BASE_URL not set" >&2; fail=1 fi if [ -z "$JIRA_USER" ] || [ -z "$JIRA_API_TOKEN" ]; then echo "ERROR: JIRA_USER and JIRA_API_TOKEN must be set" >&2; fail=1 fi [ "$fail" -eq 1 ] && exit 1 } acquire_lock() { if [ -f "$LOCK_FILE" ]; then local pid pid=$(cat "$LOCK_FILE" 2>/dev/null) if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then echo "ERROR: Another instance is already running (PID: $pid)" >&2 exit 1 else echo "Removing stale lock file" >&2 rm -f "$LOCK_FILE" fi fi echo $$ > "$LOCK_FILE" trap cleanup EXIT INT TERM } cleanup() { rm -f "$LOCK_FILE" } # --------------------------------------------------------------------------- # Jira API helper # --------------------------------------------------------------------------- jira_api() { local endpoint="$1" local url="${JIRA_BASE_URL}/rest/api/2${endpoint}" curl -s --max-time "$CURL_TIMEOUT" \ -H "Authorization: Bearer ${JIRA_API_TOKEN}" \ -H "Content-Type: application/json" \ -H "X-Atlassian-Token: no-check" \ "$url" 2>/dev/null } jira_search() { local jql="$1" local max_results="${2:-0}" local fields="${3:-}" local url="${JIRA_BASE_URL}/rest/api/2/search" local data_args=("--data-urlencode" "jql=${jql}" "--data-urlencode" "maxResults=${max_results}") if [ -n "$fields" ]; then data_args+=("--data-urlencode" "fields=${fields}") fi curl -s -G --max-time "$CURL_TIMEOUT" \ -H "Authorization: Bearer ${JIRA_API_TOKEN}" \ -H "Content-Type: application/json" \ -H "X-Atlassian-Token: no-check" \ "${data_args[@]}" \ "$url" 2>/dev/null } jira_search_count() { local jql="$1" local result result=$(jira_search "$jql" 0) echo "$result" | jq -r '.total // 0' 2>/dev/null } # Build project filter clause get_project_filter() { if [ -z "$JIRA_PROJECTS" ]; then echo "" return fi local clause="project in (" local first=true IFS=',' read -ra PROJ_ARRAY <<< "$JIRA_PROJECTS" for proj in "${PROJ_ARRAY[@]}"; do proj=$(echo "$proj" | tr -d ' ') if [ "$first" = true ]; then clause="${clause}\"${proj}\"" first=false else clause="${clause},\"${proj}\"" fi done clause="${clause})" echo "$clause" } # --------------------------------------------------------------------------- # Data collection (cached in temp dir) # --------------------------------------------------------------------------- CACHE_DIR="" cache_all_jira_data() { CACHE_DIR=$(mktemp -d /tmp/jira_metrics_cache.XXXXXX) local pf pf=$(get_project_filter) local pf_and="" [ -n "$pf" ] && pf_and="${pf} AND " # Issue counts by status for status in "To Do" "In Progress" "In Review" "Done" "Closed" "Open" "Reopened" "Resolved"; do local safe_status safe_status=$(echo "$status" | tr ' ' '_' | tr '[:upper:]' '[:lower:]') jira_search_count "${pf_and}status = \"${status}\"" > "$CACHE_DIR/status_${safe_status}" & done # Issue counts by priority for priority in "Highest" "High" "Medium" "Low" "Lowest"; do local safe_priority safe_priority=$(echo "$priority" | tr '[:upper:]' '[:lower:]') jira_search_count "${pf_and}priority = \"${priority}\"" > "$CACHE_DIR/priority_${safe_priority}" & done # Issue counts by type for itype in "Bug" "Story" "Task" "Epic" "Sub-task" "Incident"; do local safe_type safe_type=$(echo "$itype" | tr ' ' '_' | tr '-' '_' | tr '[:upper:]' '[:lower:]') jira_search_count "${pf_and}issuetype = \"${itype}\"" > "$CACHE_DIR/type_${safe_type}" & done # Total unresolved (backlog) jira_search_count "${pf_and}resolution = Unresolved" > "$CACHE_DIR/backlog_total" & # Overdue issues jira_search_count "${pf_and}due < now() AND resolution = Unresolved" > "$CACHE_DIR/overdue" & # Unassigned issues jira_search_count "${pf_and}assignee is EMPTY AND resolution = Unresolved" > "$CACHE_DIR/unassigned" & # Created in last 24h jira_search_count "${pf_and}created >= -1d" > "$CACHE_DIR/created_24h" & # Resolved in last 24h jira_search_count "${pf_and}resolved >= -1d" > "$CACHE_DIR/resolved_24h" & # Created in last 7d jira_search_count "${pf_and}created >= -7d" > "$CACHE_DIR/created_7d" & # Resolved in last 7d jira_search_count "${pf_and}resolved >= -7d" > "$CACHE_DIR/resolved_7d" & # Created in last 30d jira_search_count "${pf_and}created >= -30d" > "$CACHE_DIR/created_30d" & # Resolved in last 30d jira_search_count "${pf_and}resolved >= -30d" > "$CACHE_DIR/resolved_30d" & # Aged backlog buckets (unresolved, created more than X days ago) jira_search_count "${pf_and}resolution = Unresolved AND created <= -7d" > "$CACHE_DIR/backlog_older_7d" & jira_search_count "${pf_and}resolution = Unresolved AND created <= -30d" > "$CACHE_DIR/backlog_older_30d" & jira_search_count "${pf_and}resolution = Unresolved AND created <= -90d" > "$CACHE_DIR/backlog_older_90d" & jira_search_count "${pf_and}resolution = Unresolved AND created <= -365d" > "$CACHE_DIR/backlog_older_365d" & # High-priority unresolved jira_search_count "${pf_and}priority in (Highest, High) AND resolution = Unresolved" > "$CACHE_DIR/high_priority_unresolved" & # Critical bugs unresolved jira_search_count "${pf_and}issuetype = Bug AND priority in (Highest, High) AND resolution = Unresolved" > "$CACHE_DIR/critical_bugs" & wait } cache_project_data() { local proj_list="$JIRA_PROJECTS" if [ -z "$proj_list" ]; then proj_list=$(jira_api "/project" | jq -r '.[].key' 2>/dev/null | paste -sd ',' -) if [ -z "$proj_list" ]; then return fi JIRA_PROJECTS="$proj_list" fi IFS=',' read -ra PROJ_ARRAY <<< "$proj_list" for proj in "${PROJ_ARRAY[@]}"; do proj=$(echo "$proj" | tr -d ' ') local safe_proj safe_proj=$(echo "$proj" | tr '[:upper:]' '[:lower:]') jira_search_count "project = \"${proj}\" AND resolution = Unresolved" > "$CACHE_DIR/proj_open_${safe_proj}" & jira_search_count "project = \"${proj}\"" > "$CACHE_DIR/proj_total_${safe_proj}" & jira_search_count "project = \"${proj}\" AND created >= -7d" > "$CACHE_DIR/proj_created_7d_${safe_proj}" & jira_search_count "project = \"${proj}\" AND resolved >= -7d" > "$CACHE_DIR/proj_resolved_7d_${safe_proj}" & done wait } cache_assignee_data() { local pf pf=$(get_project_filter) local pf_and="" [ -n "$pf" ] && pf_and="${pf} AND " local result result=$(jira_search "${pf_and}resolution = Unresolved AND assignee is not EMPTY ORDER BY assignee ASC" "$MAX_RESULTS" "assignee") if [ -n "$result" ]; then echo "$result" | jq -r ' [.issues[]? | .fields.assignee.displayName // .fields.assignee.name // "unknown"] | group_by(.) | map({name: .[0], count: length}) | sort_by(-.count) | .[:15][] | "\(.name)|\(.count)" ' 2>/dev/null > "$CACHE_DIR/assignees" fi } cache_component_data() { local pf pf=$(get_project_filter) local pf_and="" [ -n "$pf" ] && pf_and="${pf} AND " local result result=$(jira_search "${pf_and}resolution = Unresolved AND component is not EMPTY" "$MAX_RESULTS" "components") if [ -n "$result" ] && echo "$result" | jq -e '.issues' >/dev/null 2>&1; then echo "$result" | jq -r ' [.issues[]? | .fields.components[]?.name // "unknown"] | group_by(.) | map({name: .[0], count: length}) | sort_by(-.count) | .[:20][] | "\(.name)|\(.count)" ' 2>/dev/null > "$CACHE_DIR/components" fi } cache_resolution_time_data() { local pf pf=$(get_project_filter) local pf_and="" [ -n "$pf" ] && pf_and="${pf} AND " local result result=$(jira_search "${pf_and}resolved >= -7d" 100 "created,resolutiondate") if [ -n "$result" ]; then echo "$result" | jq -r ' [.issues[]? | select(.fields.resolutiondate != null and .fields.created != null) | ((.fields.resolutiondate | sub("\\.[0-9]+.*";"") | strptime("%Y-%m-%dT%H:%M:%S") | mktime) - (.fields.created | sub("\\.[0-9]+.*";"") | strptime("%Y-%m-%dT%H:%M:%S") | mktime)) / 3600 ] | if length > 0 then { avg: (add / length), min: min, max: max, count: length } else { avg: 0, min: 0, max: 0, count: 0 } end | "\(.avg)|\(.min)|\(.max)|\(.count)" ' 2>/dev/null > "$CACHE_DIR/resolution_times" fi } cache_login_data() { local security_log="${JIRA_HOME}/log/atlassian-jira-security.log" if [ ! -f "$security_log" ]; then echo "0" > "$CACHE_DIR/logins_total" echo "0" > "$CACHE_DIR/logins_success" echo "0" > "$CACHE_DIR/logins_failed" echo "0" > "$CACHE_DIR/logins_unique_users" return fi local since_date since_date=$(date -d '24 hours ago' '+%Y-%m-%d %H:%M' 2>/dev/null || \ date -v-1d '+%Y-%m-%d %H:%M' 2>/dev/null) # Extract lines from last 24h (compare date prefix YYYY-MM-DD HH:MM) local recent_lines recent_lines=$(awk -v since="$since_date" '$0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}/ { ts=substr($0,1,16); if (ts >= since) print }' "$security_log" 2>/dev/null) if [ -z "$recent_lines" ]; then echo "0" > "$CACHE_DIR/logins_total" echo "0" > "$CACHE_DIR/logins_success" echo "0" > "$CACHE_DIR/logins_failed" echo "0" > "$CACHE_DIR/logins_unique_users" return fi # Successful logins: "has PASSED authentication" local success_count success_count=$(echo "$recent_lines" | grep -c 'has PASSED authentication' 2>/dev/null) echo "${success_count:-0}" > "$CACHE_DIR/logins_success" # Failed logins: "tried to login but" local failed_count failed_count=$(echo "$recent_lines" | grep -c 'tried to login but' 2>/dev/null) echo "${failed_count:-0}" > "$CACHE_DIR/logins_failed" # Total login events local total_count total_count=$(( success_count + failed_count )) echo "${total_count:-0}" > "$CACHE_DIR/logins_total" # Unique users (extract username from 'user' has PASSED) local unique_users unique_users=$(echo "$recent_lines" | grep 'has PASSED authentication' | \ grep -oP "The user '\K[^']+" 2>/dev/null | \ sort -u | wc -l) echo "${unique_users:-0}" > "$CACHE_DIR/logins_unique_users" # Per-user login counts (top 15) echo "$recent_lines" | grep 'has PASSED authentication' | \ grep -oP "The user '\K[^']+" 2>/dev/null | \ sort | uniq -c | sort -rn | head -15 | \ awk '{print $2"|"$1}' > "$CACHE_DIR/logins_per_user" 2>/dev/null } # --------------------------------------------------------------------------- # Plugin / add-on metrics (UPM API - Jira Server/DC) # --------------------------------------------------------------------------- cache_plugin_data() { local result result=$(curl -s --max-time "$CURL_TIMEOUT" \ -H "Authorization: Bearer ${JIRA_API_TOKEN}" \ -H "Accept: application/vnd.atl.plugins.installed+json" \ "${JIRA_BASE_URL}/rest/plugins/1.0/" 2>/dev/null) if [ -n "$result" ] && echo "$result" | jq -e '.plugins' >/dev/null 2>&1; then echo "$result" | jq -r '.plugins | length' > "$CACHE_DIR/plugins_total" 2>/dev/null echo "$result" | jq -r '[.plugins[]? | select(.enabled == true)] | length' > "$CACHE_DIR/plugins_enabled" 2>/dev/null echo "$result" | jq -r '[.plugins[]? | select(.enabled == false)] | length' > "$CACHE_DIR/plugins_disabled" 2>/dev/null echo "$result" | jq -r '[.plugins[]? | select(.userInstalled == true)] | length' > "$CACHE_DIR/plugins_user_installed" 2>/dev/null echo "$result" | jq -r '[.plugins[]? | select(.userInstalled == false or .userInstalled == null)] | length' > "$CACHE_DIR/plugins_system" 2>/dev/null echo "$result" | jq -r ' [.plugins[]? | select(.userInstalled == true)] | .[] | "\(.name // .key)|\(if .enabled then 1 else 0 end)|\(.version // "unknown")" ' 2>/dev/null > "$CACHE_DIR/plugins_detail" else echo "0" > "$CACHE_DIR/plugins_total" echo "0" > "$CACHE_DIR/plugins_enabled" echo "0" > "$CACHE_DIR/plugins_disabled" echo "0" > "$CACHE_DIR/plugins_user_installed" echo "0" > "$CACHE_DIR/plugins_system" fi } # --------------------------------------------------------------------------- # Service Desk metrics (Jira Service Desk / Service Management API) # --------------------------------------------------------------------------- servicedesk_api() { local endpoint="$1" curl -s --max-time "$CURL_TIMEOUT" \ -H "Authorization: Bearer ${JIRA_API_TOKEN}" \ -H "Content-Type: application/json" \ -H "X-ExperimentalApi: opt-in" \ "${JIRA_BASE_URL}/rest/servicedeskapi${endpoint}" 2>/dev/null } cache_servicedesk_data() { local sd_list sd_list=$(servicedesk_api "/servicedesk") if [ -z "$sd_list" ] || ! echo "$sd_list" | jq -e '.values' >/dev/null 2>&1; then echo "0" > "$CACHE_DIR/sd_available" return fi echo "1" > "$CACHE_DIR/sd_available" local sd_count sd_count=$(echo "$sd_list" | jq -r '.size // (.values | length)' 2>/dev/null) echo "${sd_count:-0}" > "$CACHE_DIR/sd_count" echo "$sd_list" | jq -r '.values[]? | "\(.id)|\(.projectKey // "unknown")|\(.projectName // "unknown")"' \ 2>/dev/null > "$CACHE_DIR/sd_list" local tmp_queues="$CACHE_DIR/sd_queues_all" local tmp_request_types="$CACHE_DIR/sd_request_types_all" local tmp_orgs="$CACHE_DIR/sd_orgs_all" : > "$tmp_queues" : > "$tmp_request_types" : > "$tmp_orgs" while IFS='|' read -r sd_id sd_key _sd_name; do [ -z "$sd_id" ] && continue local queues queues=$(servicedesk_api "/servicedesk/${sd_id}/queue") if [ -n "$queues" ] && echo "$queues" | jq -e '.values' >/dev/null 2>&1; then echo "$queues" | jq -r --arg proj "$sd_key" ' .values[]? | "\($proj)|\(.id)|\(.name // "unknown")|\(.issueCount // 0)" ' 2>/dev/null >> "$tmp_queues" fi local rtypes rtypes=$(servicedesk_api "/servicedesk/${sd_id}/requesttype") if [ -n "$rtypes" ] && echo "$rtypes" | jq -e '.values' >/dev/null 2>&1; then echo "$rtypes" | jq -r --arg proj "$sd_key" ' .values[]? | "\($proj)|\(.id)|\(.name // "unknown")" ' 2>/dev/null >> "$tmp_request_types" fi local orgs orgs=$(servicedesk_api "/servicedesk/${sd_id}/organization") if [ -n "$orgs" ] && echo "$orgs" | jq -e '.values' >/dev/null 2>&1; then local org_count org_count=$(echo "$orgs" | jq -r '.size // (.values | length)' 2>/dev/null) echo "${sd_key}|${org_count:-0}" >> "$tmp_orgs" fi done < "$CACHE_DIR/sd_list" local pf pf=$(get_project_filter) local pf_and="" [ -n "$pf" ] && pf_and="${pf} AND " jira_search_count "${pf_and}\"Customer Request Type\" is not EMPTY AND resolution = Unresolved" \ > "$CACHE_DIR/sd_open_requests" 2>/dev/null & jira_search_count "${pf_and}\"Customer Request Type\" is not EMPTY AND resolved >= -24h" \ > "$CACHE_DIR/sd_resolved_24h" 2>/dev/null & jira_search_count "${pf_and}\"Customer Request Type\" is not EMPTY AND resolved >= -7d" \ > "$CACHE_DIR/sd_resolved_7d" 2>/dev/null & jira_search_count "${pf_and}\"Customer Request Type\" is not EMPTY AND created >= -24h" \ > "$CACHE_DIR/sd_created_24h" 2>/dev/null & jira_search_count "${pf_and}\"Customer Request Type\" is not EMPTY AND created >= -7d" \ > "$CACHE_DIR/sd_created_7d" 2>/dev/null & jira_search_count "${pf_and}\"Customer Request Type\" is not EMPTY AND priority in (Highest, High) AND resolution = Unresolved" \ > "$CACHE_DIR/sd_high_priority_open" 2>/dev/null & jira_search_count "${pf_and}\"Customer Request Type\" is not EMPTY AND due < now() AND resolution = Unresolved" \ > "$CACHE_DIR/sd_breached_sla" 2>/dev/null & wait local csat_result csat_result=$(jira_search "${pf_and}\"Customer Request Type\" is not EMPTY AND resolved >= -30d AND \"Satisfaction rating\" is not EMPTY" \ 100 "customfield_10421,satisfaction") if [ -n "$csat_result" ] && echo "$csat_result" | jq -e '.issues' >/dev/null 2>&1; then local csat_total csat_total=$(echo "$csat_result" | jq -r '.total // 0' 2>/dev/null) echo "${csat_total}" > "$CACHE_DIR/sd_csat_responses" else echo "0" > "$CACHE_DIR/sd_csat_responses" fi } # --------------------------------------------------------------------------- # Read cached values # --------------------------------------------------------------------------- read_cache() { local key="$1" default="${2:-0}" local val val=$(tr -d '[:space:]' < "$CACHE_DIR/$key" 2>/dev/null) if [ -z "$val" ] || [ "$val" = "null" ]; then echo "$default" else echo "$val" fi } # --------------------------------------------------------------------------- # Metric generation # --------------------------------------------------------------------------- generate_metrics() { local start_time start_time=$(date +%s) # Read all cached values local status_todo status_in_progress status_in_review status_done status_closed status_open status_reopened status_resolved status_todo=$(read_cache "status_to_do") status_in_progress=$(read_cache "status_in_progress") status_in_review=$(read_cache "status_in_review") status_done=$(read_cache "status_done") status_closed=$(read_cache "status_closed") status_open=$(read_cache "status_open") status_reopened=$(read_cache "status_reopened") status_resolved=$(read_cache "status_resolved") local priority_highest priority_high priority_medium priority_low priority_lowest priority_highest=$(read_cache "priority_highest") priority_high=$(read_cache "priority_high") priority_medium=$(read_cache "priority_medium") priority_low=$(read_cache "priority_low") priority_lowest=$(read_cache "priority_lowest") local type_bug type_story type_task type_epic type_sub_task type_incident type_bug=$(read_cache "type_bug") type_story=$(read_cache "type_story") type_task=$(read_cache "type_task") type_epic=$(read_cache "type_epic") type_sub_task=$(read_cache "type_sub_task") type_incident=$(read_cache "type_incident") local backlog_total overdue unassigned backlog_total=$(read_cache "backlog_total") overdue=$(read_cache "overdue") unassigned=$(read_cache "unassigned") local created_24h resolved_24h created_7d resolved_7d created_30d resolved_30d created_24h=$(read_cache "created_24h") resolved_24h=$(read_cache "resolved_24h") created_7d=$(read_cache "created_7d") resolved_7d=$(read_cache "resolved_7d") created_30d=$(read_cache "created_30d") resolved_30d=$(read_cache "resolved_30d") local backlog_older_7d backlog_older_30d backlog_older_90d backlog_older_365d backlog_older_7d=$(read_cache "backlog_older_7d") backlog_older_30d=$(read_cache "backlog_older_30d") backlog_older_90d=$(read_cache "backlog_older_90d") backlog_older_365d=$(read_cache "backlog_older_365d") local high_priority_unresolved critical_bugs high_priority_unresolved=$(read_cache "high_priority_unresolved") critical_bugs=$(read_cache "critical_bugs") cat </dev/null) if [ -n "$rt_data" ] && [ "$rt_data" != "null" ]; then rt_avg=$(echo "$rt_data" | cut -d'|' -f1) rt_min=$(echo "$rt_data" | cut -d'|' -f2) rt_max=$(echo "$rt_data" | cut -d'|' -f3) rt_count=$(echo "$rt_data" | cut -d'|' -f4) fi fi cat <&2 while true; do { read -r request if [[ "$request" =~ ^GET\ /metrics ]]; then printf "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4; charset=utf-8\r\n\r\n" cache_all_jira_data cache_project_data cache_assignee_data cache_component_data cache_resolution_time_data cache_login_data cache_plugin_data cache_servicedesk_data generate_metrics else printf "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n" echo "

Jira Exporter v${SCRIPT_VERSION}

Metrics" fi } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null done } # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- main() { parse_args "$@" preflight_check [ "$HTTP_MODE" != true ] && acquire_lock if [ "$HTTP_MODE" = true ]; then run_http_server elif [ -n "$OUTPUT_FILE" ]; then cache_all_jira_data cache_project_data cache_assignee_data cache_component_data cache_resolution_time_data cache_login_data cache_plugin_data cache_servicedesk_data mkdir -p "$(dirname "$OUTPUT_FILE")" local temp_file temp_file=$(mktemp /tmp/jira_metrics.XXXXXX) generate_metrics > "$temp_file" rm -f "$OUTPUT_FILE" mv "$temp_file" "$OUTPUT_FILE" chmod 644 "$OUTPUT_FILE" sync else cache_all_jira_data cache_project_data cache_assignee_data cache_component_data cache_resolution_time_data cache_login_data cache_plugin_data cache_servicedesk_data generate_metrics fi } main "$@"