SLA の実際の仕組みIssue SLAエンジンには多くの可動部品があり、エンジン自体は過去数年間に何度も変更されています。この記事の内容: 2011 エンジン (com.snc.sla.engine.version) と呼ばれる現在の SLA エンジンの動作について簡単に説明します。SLA 問題のトラブルシューティング手順を示します。 しくみ イベント駆動型の観点から見ると、SLA プロセスはタスクテーブルから始まります。誰かまたは何かが次のいずれかを実行します。 インシデントを送信問題の更新サービスカタログアイテムを要求 これが発生すると、SLA の実行 という名前のビジネスルールが開始されます。このビジネスルールは、 TaskSLAController という名前のスクリプトインクルードを呼び出します。TaskSLAController は SLA エンジンへのゲートウェイです。 移行 2011 年 6 月の SLA エンジンの書き直しの時点で、SLA の実際の作業のほとんどはスクリプトインクルードによって処理されています。コードは、拡張とカスタマイズを可能にする、再利用可能でカプセル化された方法で記述されています。また、コードがはるかに理解しやすくなり、タスクを論理的な部分にグループ化できます。おそらく、このカプセル化の最も創造的で役立つ例は、ステージ間の遷移を処理するコードです。この重要なコンポーネントは、カスタム機能が必要な場合に簡単に上書きできるメソッドを備えた基底クラスのスクリプトインクルード (SLAConditionBase) です。(詳細については、条件ルールを参照してください。ステージが 達成 になるはずなのに、不思議なことに 完了 に変更されていることに気付いた場合は、このメカニズムの一部が正しく構成されていない可能性があります。計算または SLA 操作に関するシステム上の問題に気付いた場合は、コアスクリプトインクルードの 1 つがカスタマイズされており、前回のアップグレード時に更新されていない可能性があります。 ワークフロー 2010 エンジン (2010 年春以降のパッチの 1 つで導入) 以降、SLA ワークフローは通知目的でのみ使用されてきました。それ以前は、フィールドの更新やエスカレーションの管理に使用されていました。予期した通知が届かない場合、または正しいタイミングで通知が届かない場合は、Workflow を使用して問題を探します。 違反タイマー 違反タイマーはsys_triggerレコードです ( システムスケジューラー>スケジュール済みジョブ モジュール内)。これは、システムでスケジュールされているすべてのアイテムを処理する非常に中心的なテーブルです。SLA が 処理中 に移行するたびに、違反タイマーが生成され、 予定終了時間 フィールドに指定された時刻に期限切れになります。違反タイマーが期限切れになると、TaskSLA.breachTimerExpired() を呼び出します。違反タイマーの問題は多くありません。問題が発生した場合は、インシデントが必要以上に遅れて違反したように見えるかもしれません。Sys_triggerレコードは再起動中に破棄されることはなく、メモリ不足エラーなどの極端なエラーが発生しない限り、実行を停止する通常の理由はありません。sys_triggerキューがバックアップされると、キュー内のすべてのジョブの実行が停止します。SLA は違反を阻止するだけでなく、ワークフロータイマーもsys_triggerレコードで実行されるため、ワークフロー通知は送信されません。スケジュール可能なものはすべてsys_triggerテーブルでスケジュールされます。 表示時に計算 SLA を更新するものは何ですか ?SLA について報告し、同じレポートを 2 回実行した場合、[ 処理中 SLA の経過割合フィールドは変更されないことがわかります。場合によっては、顧客が ServiceNow カスタマーサポートに電話して、なぜインシデントをオープンできるのか疑問に思っていて、20 秒後に 経過割合 フィールドを見てもゼロのままであるということがあります。問題は、SLA の計算フィールドがリアルタイムで魔法のようにアップグレードされるわけではないことです。glide.sla.calculate_on_displayプロパティをオンにしない限り、インシデントを見ても計算はアップグレードされません。その理由は、SLA 計算はイベントが発生したときにのみ更新されるためです。デフォルトでは、これは次の場合です。 task_slaが移行するための条件が満たされている。違反タイマーの期限が切れます。計算ジョブの実行 (詳細は以下を参照)。 glide.sla.calculate_on_display システムプロパティ (sys_propertyテーブル) をオンにしても、task_slaテーブルを直接、またはレポートエンジンで表示したときに期待した結果は得られません。これは、[表示時に計算 (Calculate on Display)] ビジネスルールが、ユーザーがtask_slaレコードではなくタスクレコードを参照するときにのみ実行されるためです。SLA レポートを計画および設計するときは、この点を考慮してください。 計算ジョブ 計算ジョブは、さまざまな間隔で実行されるスケジュール済みジョブ (sys_triggerテーブル) であり、まもなく違反する、またはすでに違反している SLA の計算値を更新します。これらの計算ジョブは、違反に近い SLA の計算値の信頼性を高めるのに役立ちます。たとえば、[スケジュール済みジョブ] テーブルで「 SLA Update で始まるジョブを検索すると、多数のジョブが見つかります。スケジュール済みジョブの 1 つが毎分実行され、10 分以内に違反するすべての SLA が更新されます。 トラブルシューティング ほとんどの SLA 問題は、解決に時間と労力を要します。場合によっては、SLA エンジンがどのように動作すべきかを理解することに重点が置かれます (この記事を読んでくれた皆さんに拍手を送ります)。多くの企業にとって、SLA は主要なパフォーマンス指標であるため、時間と労力は価値があります。 最初に行う最善の方法は、予想される動作を正確に判断することです。上記のさまざまな領域を検討してください。予期せぬ動作をしていると思われる動作は、どのようなメカニズムで制御されているのでしょうか?実行している SLA エンジンのバージョンは何ですか。カスタマイズはありますか ? デスクチェック このバックグラウンドスクリプトは、関連する詳細のほとんどすべてを取得し、タイムラインに出力します。このスクリプトは表示側ではかなり単純ですが、次のようになります。 スケジュール、ワークフロー、および SLA 定義のすべての構成情報を出力します。アクティブなワークフローコンテキストのアクティビティ、タスク SLA 移行に影響するタスクフィールドへの変更、および SLA 計算自体の現在の値など、関連するすべてのアクティビティのタイムラインを出力します。 スクリプトはタスク SLA 計算の更新を強制しないため、精度は最後の計算時間と同程度であることに注意してください。(これは将来追加すると便利な機能です。 /* * @Usage - Currently the following two examples are the way I * imagine this object being used. The first example will return a * report of a specified number of task_sla's. The second example * takes a sys_id of a task_sla record and returns a report about * that task_sla. */ var dCheck = newDCheck(); var filters = [{ "field": "end_time", "operator": "!=", "value": "" }, { "field": "sys_created_on", "operator": ">", "value": "2012-04-13 21:00:00" }, { "field": "sys_created_on", "operator": "<", "value": "2012-04-13 23:00:00" } ]; gs.print(dCheck.getLatestByDefinition('name', 'ADSK - Incident- P3 Resolution(5 Days) ', 3, filters)); gs.print(dCheck.getSlaTimeline('5161366d0a0a0b3000d59bf577381424')); /* * @Description - This script speeds up the information gathering process * about an existing task_sla. It outputs information about the * contract_sla (SLA Definition), task (Incident, Problem, etc.), task_sla * (running SLA), wf_context (Running Workflow) and wf_history (History * of the running Workflow). First it outputs information about the * definitions of the above items and then it compiles the history of * changes to each item and orders them by timestamp so that you can piece * together expected vs. actual behavior. See bottom of script for usage * information. */ function newDCheck() { return { //Specify sys_id of the SLA definition //@param fld {string} field of SLA Definition to query //@param value {string} value to match agains fld //@param filters {array} array of objects in format [{field:"",op:"",value:""},...] getLatestByDefinition: function(fld, value, test, filters) { if (typeof tests == "undefined") tests = 3; //number of sla's to test var out = ["NOTE: All times in GMT\n"]; var sld = new GlideRecord("contract_sla"); sld.addQuery(fld, value); sld.query(); while (sld.next()) { //Print conditions from SLA definition out.push(this.printSlaDefinition(sld)); //Pull out the field names var flds = this.getSlaConditions(sld); out.push("fields from conditions: " + flds.join()); //Print information about the SLA schedule SKIPPED //Get group of task_sla records var slt = new GlideRecord("task_sla"); slt.addQuery("sla", sld.sys_id + ""); slt.addQuery("sys_created_on", "<", gs.daysAgoStart(1)); if (filters) for (var iz = 0; iz < filters.length; iz++) slt.addQuery(filters[iz].field, filters[iz].operator, filters[iz].value); slt.orderByDesc("sys_created_on"); slt.setLimit(tests); slt.query(); //For each task_sla id = 0; while (slt.next()) { out.push("\n*******" + ++id + "*******"); out.push(this.getSlaTimeline(slt, flds)); } } return out.join("\n"); }, printSlaDefinition: function(contract) { var out = []; var sldFlds = ['name', 'workflow', 'collection', 'duration', 'duration_type', 'retroactive', 'set_start_to', 'schedule', 'timezone', 'type', 'start_condition', 'stop_condition', 'pause_condition', 'reset_condition']; for (var ia = 0; ia < sldFlds.length; ia++) out.push("contract_sla." + sldFlds[ia] + ": " + contract[sldFlds[ia]]); return out.join("\n"); }, getSlaConditions: function(contract) { var conds = ['start_condition', 'stop_condition', 'pause_condition', 'reset_condition']; var flds = []; for (var ib = 0; ib < conds.length; ib++) { var cond = contract[conds[ib]].split("^"); for (var ic = 0; ic < cond.length; ic++) { var fld = cond[ic].split(/[^a-z_]/)[0]; if (fld) flds.push(fld); } } return flds; }, timeline: { _timeline: [], sort: function() { return this._timeline.sort(); }, push: function(value) { var i = this._timeline.length; while (i--) if (this._timeline[i] === value) return; this._timeline.push(value); }, join: function(delim) { return this._timeline.join(delim); }, reset: function() { this._timeline = new Array(); } }, getSlaTimeline: function(sla, condFlds) { //var timeline = []; var fields = []; if (typeof sla == "string") { var sltask = new GlideRecord("task_sla"); sltask.get(sla); sla = sltask; fields.push(this.printSlaDefinition(sla.sla)); } else if (!sla) return; if (!condFlds) condFlds = this.getSlaConditions(sla.sla); //Print task information (retroactive value, any field in conditions...) if (sla.task) { var taskRec = new GlideRecord("task"); taskRec.addQuery("sys_id", sla.task + ""); taskRec.query(); if (taskRec.next()) { var tFlds = ['sys_id', 'number', sla.sla.set_start_to]; for (var ih = 0; ih < tFlds.length; ih++) fields.push("task." + tFlds[ih] + ": " + sla.task[tFlds[ih]]); //Add task information to timeline (sys_created_on, sys_updated_on...) for (var ii = 0; ii < condFlds.length; ii++) { //Query audit tables for all field names from the conditions list ordered by Created On var aud = new GlideRecord("sys_audit"); aud.addQuery("documentkey", sla.task.sys_id + ""); aud.addQuery("fieldname", condFlds[ii]); aud.orderBy("sys_created_on"); aud.query(); if (aud.getRowCount() == 0) this.timeline.push(sla.task.sys_created_on + " Orig " + condFlds[ii] + ": " + sla.task[condFlds[ii]]); while (aud.next()) { this.timeline.push(aud.sys_created_on + " Task " + aud.fieldname + " old:" + aud.oldvalue + " new:" + aud.newvalue); } //Add audit information to timeline } } else { //bad reference fields.push("ERROR: task is a bad reference"); } } //Print task_sla information var sFlds = ['sys_id', 'task', 'start_time', 'end_time', 'planned_end_time', 'duration', 'percentage', 'pause_time', 'pause_duration', 'stage', 'has_breached', 'sys_created_on', 'sys_updated_on']; for (var ie = 0; ie < sFlds.length; ie++) fields.push("task_sla." + sFlds[ie] + ": " + sla[sFlds[ie]]); //Add task_sla information to timeline (start_time, end_time, planned_end) var tlFlds = ['start_time', 'end_time', 'pause_time', 'planned_end_time', 'sys_created_on', 'sys_updated_on']; for (var ig = 0; ig < tlFlds.length; ig++) this.timeline.push(sla[tlFlds[ig]] + " SLA " + tlFlds[ig]); //Query wf_context for this task_sla var wf = new GlideRecord("wf_context"); //Print wf_context info wf.addQuery("id", sla.sys_id + ""); wf.query(); //Query all Activities in the wf_context while (wf.next()) { fields.push("wf_context.name: " + wf.name); fields.push("wf_context.started: " + wf.started); fields.push("wf_context.ended: " + wf.ended); //Add Activity start/end to timeline var wfe = new GlideRecord("wf_history"); wfe.addQuery("context", wf.sys_id + ""); wfe.query(); while (wfe.next()) { this.timeline.push(wfe.started + " WF " + wfe.activity.name + " (activity) began"); this.timeline.push(wfe.ended + " WF " + wfe.activity.name + " (activity) ended"); } } var slaTimeline = fields.join("\n") + "\n**Timeline (GMT)**\n" + this.timeline.sort().join("\n"); this.timeline.reset(); return slaTimeline; } } }