monitoring.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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:
  39. product === "go"
  40. ? `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401")), NOT(EQUALS($status, "429"))), 1, 0)`
  41. : `IF(AND(GTE($status, "400"), NOT(EQUALS($status, "401"))), 1, 0)`,
  42. },
  43. ],
  44. calculations: [
  45. { op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
  46. { op: "SUM", name: "FAILED", column: "is_failed_http_status", filterCombination: "AND", filters },
  47. ],
  48. formulas: [{ name: "ERROR", expression: "IF(GTE($TOTAL, 100), DIV($FAILED, $TOTAL), 0)" }],
  49. timeRange: 900,
  50. }).json
  51. }
  52. const providerHttpErrorsQuery = (product: "go" | "zen") => {
  53. const filters = [
  54. { column: "provider", op: "exists" },
  55. { column: "user_agent", op: "contains", value: "opencode" },
  56. { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
  57. ]
  58. return honeycomb.getQuerySpecificationOutput({
  59. breakdowns: ["provider"],
  60. calculatedFields: [
  61. {
  62. name: "is_success_http_status",
  63. expression: `IF(AND(GTE($status, "200"), LT($status, "400")), 1, 0)`,
  64. },
  65. {
  66. name: "is_failed_provider_http_status",
  67. expression: `IF(GT($llm.error.code, "400"), 1, 0)`,
  68. },
  69. ],
  70. calculations: [
  71. {
  72. op: "SUM",
  73. name: "SUCCESS",
  74. column: "is_success_http_status",
  75. filterCombination: "AND",
  76. filters: [...filters, { column: "event_type", op: "=", value: "completions" }],
  77. },
  78. {
  79. op: "SUM",
  80. name: "FAILED",
  81. column: "is_failed_provider_http_status",
  82. filterCombination: "AND",
  83. filters: [...filters, { column: "event_type", op: "=", value: "llm.error" }],
  84. },
  85. ],
  86. formulas: [
  87. { name: "ERROR", expression: "IF(GTE(SUM($SUCCESS, $FAILED), 50), DIV($FAILED, SUM($SUCCESS, $FAILED)), 0)" },
  88. ],
  89. timeRange: 900,
  90. }).json
  91. }
  92. const description = "Managed by SST (Don't edit in Honeycomb UI)"
  93. new honeycomb.Trigger("IncreasedModelHttpErrorsGo", {
  94. name: "Increased Model HTTP Errors [Go]",
  95. description,
  96. queryJson: modelHttpErrorsQuery("go"),
  97. alertType: "on_change",
  98. frequency: 300,
  99. thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
  100. recipients: [
  101. {
  102. id: webhookRecipient.id,
  103. notificationDetails: [
  104. {
  105. variables: [{ name: "type", value: "model_http_errors" }],
  106. },
  107. ],
  108. },
  109. ],
  110. })
  111. new honeycomb.Trigger("IncreasedModelHttpErrorsZen", {
  112. name: "Increased Model HTTP Errors [Zen]",
  113. description,
  114. queryJson: modelHttpErrorsQuery("zen"),
  115. alertType: "on_change",
  116. frequency: 300,
  117. thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
  118. recipients: [
  119. {
  120. id: webhookRecipient.id,
  121. notificationDetails: [
  122. {
  123. variables: [{ name: "type", value: "model_http_errors" }],
  124. },
  125. ],
  126. },
  127. ],
  128. })
  129. new honeycomb.Trigger("IncreasedProviderHttpErrorsGo", {
  130. name: "Increased Provider HTTP Errors [Go]",
  131. description,
  132. queryJson: providerHttpErrorsQuery("go"),
  133. alertType: "on_change",
  134. frequency: 300,
  135. thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
  136. recipients: [
  137. {
  138. id: webhookRecipient.id,
  139. notificationDetails: [
  140. {
  141. variables: [{ name: "type", value: "provider_http_errors" }],
  142. },
  143. ],
  144. },
  145. ],
  146. })
  147. new honeycomb.Trigger("IncreasedProviderHttpErrorsZen", {
  148. name: "Increased Provider HTTP Errors [Zen]",
  149. description,
  150. queryJson: providerHttpErrorsQuery("zen"),
  151. alertType: "on_change",
  152. frequency: 300,
  153. thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
  154. recipients: [
  155. {
  156. id: webhookRecipient.id,
  157. notificationDetails: [
  158. {
  159. variables: [{ name: "type", value: "provider_http_errors" }],
  160. },
  161. ],
  162. },
  163. ],
  164. })
  165. new honeycomb.Trigger("IncreasedFreeTierRequests", {
  166. name: "Increased Free Tier Requests",
  167. description,
  168. queryJson: honeycomb.getQuerySpecificationOutput({
  169. calculations: [{ op: "COUNT" }],
  170. filters: [
  171. { column: "event_type", op: "=", value: "completions" },
  172. { column: "user_agent", op: "contains", value: "opencode" },
  173. { column: "isFreeTier", op: "=", value: "true" },
  174. ],
  175. timeRange: 3600,
  176. }).json,
  177. alertType: "on_change",
  178. frequency: 900,
  179. thresholds: [{ op: ">=", value: 50, exceededLimit: 1 }],
  180. baselineDetails: [{ type: "percentage", offsetMinutes: 1440 }],
  181. recipients: [
  182. {
  183. id: webhookRecipient.id,
  184. notificationDetails: [
  185. {
  186. variables: [{ name: "type", value: "custom" }],
  187. },
  188. ],
  189. },
  190. ],
  191. })