monitoring.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { SECRET } from "./secret"
  2. import { domain } from "./stage"
  3. const webhookRecipient = new honeycomb.WebhookRecipient("DiscordAlerts", {
  4. name: $app.stage === "production" ? "Discord Alerts" : `Discord Alerts (${$app.stage})`,
  5. url: `https://${domain}/honeycomb/webhook`,
  6. secret: SECRET.HoneycombWebhookSecret.result,
  7. templates: [
  8. {
  9. type: "trigger",
  10. body: `{
  11. "url": {{ .Result.URL | quote }},
  12. "type": {{ .Vars.type | quote }},
  13. "name": {{ .Name | quote }},
  14. "status": {{ .Alert.Status | quote }},
  15. "isTest": {{ .Alert.IsTest }},
  16. "groups": {{ .Result.GroupsTriggered | toJson }}
  17. }`,
  18. },
  19. ],
  20. variables: [
  21. {
  22. name: "type",
  23. },
  24. ],
  25. })
  26. const modelHttpErrorsQuery = (product: "go" | "zen") => {
  27. const filters = [
  28. { column: "model", op: "exists" },
  29. { column: "event_type", op: "=", value: "completions" },
  30. { column: "user_agent", op: "contains", value: "opencode" },
  31. { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
  32. ]
  33. return honeycomb.getQuerySpecificationOutput({
  34. breakdowns: ["model"],
  35. calculatedFields: [
  36. {
  37. name: "is_failed_http_status",
  38. expression: `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401"))), 1, 0)`,
  39. },
  40. ],
  41. calculations: [
  42. { op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
  43. { op: "SUM", name: "FAILED", column: "is_failed_http_status", filterCombination: "AND", filters },
  44. ],
  45. formulas: [{ name: "ERROR", expression: "IF(GTE($TOTAL, 500), DIV($FAILED, $TOTAL), 0)" }],
  46. timeRange: 900,
  47. }).json
  48. }
  49. const providerHttpErrorsQuery = (product: "go" | "zen") => {
  50. const filters = [
  51. { column: "provider", op: "exists" },
  52. { column: "user_agent", op: "contains", value: "opencode" },
  53. { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
  54. ]
  55. return honeycomb.getQuerySpecificationOutput({
  56. breakdowns: ["provider"],
  57. calculatedFields: [
  58. {
  59. name: "is_success_http_status",
  60. expression: `IF(AND(GTE($status, "200"), LT($status, "400")), 1, 0)`,
  61. },
  62. {
  63. name: "is_failed_provider_http_status",
  64. expression: `IF(GTE($llm.error.code, "400"), 1, 0)`,
  65. },
  66. ],
  67. calculations: [
  68. {
  69. op: "SUM",
  70. name: "SUCCESS",
  71. column: "is_success_http_status",
  72. filterCombination: "AND",
  73. filters: [...filters, { column: "event_type", op: "=", value: "completions" }],
  74. },
  75. {
  76. op: "SUM",
  77. name: "FAILED",
  78. column: "is_failed_provider_http_status",
  79. filterCombination: "AND",
  80. filters: [...filters, { column: "event_type", op: "=", value: "llm.error" }],
  81. },
  82. ],
  83. formulas: [
  84. { name: "ERROR", expression: "IF(GTE(SUM($SUCCESS, $FAILED), 250), DIV($FAILED, SUM($SUCCESS, $FAILED)), 0)" },
  85. ],
  86. timeRange: 1800,
  87. }).json
  88. }
  89. const description = "Managed by SST (Don't edit in Honeycomb UI)"
  90. new honeycomb.Trigger("IncreasedModelHttpErrorsGo", {
  91. name: "Increased Model HTTP Errors [Go]",
  92. description,
  93. queryJson: modelHttpErrorsQuery("go"),
  94. alertType: "on_change",
  95. frequency: 300,
  96. thresholds: [{ op: ">=", value: 0.8, exceededLimit: 1 }],
  97. recipients: [
  98. {
  99. id: webhookRecipient.id,
  100. notificationDetails: [
  101. {
  102. variables: [{ name: "type", value: "model_http_errors" }],
  103. },
  104. ],
  105. },
  106. ],
  107. })
  108. new honeycomb.Trigger("IncreasedModelHttpErrorsZen", {
  109. name: "Increased Model HTTP Errors [Zen]",
  110. description,
  111. queryJson: modelHttpErrorsQuery("zen"),
  112. alertType: "on_change",
  113. frequency: 300,
  114. thresholds: [{ op: ">=", value: 0.8, exceededLimit: 1 }],
  115. recipients: [
  116. {
  117. id: webhookRecipient.id,
  118. notificationDetails: [
  119. {
  120. variables: [{ name: "type", value: "model_http_errors" }],
  121. },
  122. ],
  123. },
  124. ],
  125. })
  126. new honeycomb.Trigger("IncreasedProviderHttpErrorsGo", {
  127. name: "Increased Provider HTTP Errors [Go]",
  128. description,
  129. queryJson: providerHttpErrorsQuery("go"),
  130. alertType: "on_change",
  131. frequency: 600,
  132. thresholds: [{ op: ">=", value: 0.8, exceededLimit: 1 }],
  133. recipients: [
  134. {
  135. id: webhookRecipient.id,
  136. notificationDetails: [
  137. {
  138. variables: [{ name: "type", value: "provider_http_errors" }],
  139. },
  140. ],
  141. },
  142. ],
  143. })
  144. new honeycomb.Trigger("IncreasedProviderHttpErrorsZen", {
  145. name: "Increased Provider HTTP Errors [Zen]",
  146. description,
  147. queryJson: providerHttpErrorsQuery("zen"),
  148. alertType: "on_change",
  149. frequency: 600,
  150. thresholds: [{ op: ">=", value: 0.8, exceededLimit: 1 }],
  151. recipients: [
  152. {
  153. id: webhookRecipient.id,
  154. notificationDetails: [
  155. {
  156. variables: [{ name: "type", value: "provider_http_errors" }],
  157. },
  158. ],
  159. },
  160. ],
  161. })
  162. new honeycomb.Trigger("IncreasedFreeTierRequests", {
  163. name: "Increased Free Tier Requests",
  164. description,
  165. queryJson: honeycomb.getQuerySpecificationOutput({
  166. calculations: [{ op: "COUNT" }],
  167. filters: [
  168. { column: "event_type", op: "=", value: "completions" },
  169. { column: "user_agent", op: "contains", value: "opencode" },
  170. { column: "isFreeTier", op: "=", value: "true" },
  171. ],
  172. timeRange: 3600,
  173. }).json,
  174. alertType: "on_change",
  175. frequency: 900,
  176. thresholds: [{ op: ">=", value: 60, exceededLimit: 1 }],
  177. baselineDetails: [{ type: "percentage", offsetMinutes: 1440 }],
  178. recipients: [
  179. {
  180. id: webhookRecipient.id,
  181. notificationDetails: [
  182. {
  183. variables: [{ name: "type", value: "custom" }],
  184. },
  185. ],
  186. },
  187. ],
  188. })